diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /unotools/source/ucbhelper | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'unotools/source/ucbhelper')
-rw-r--r-- | unotools/source/ucbhelper/XTempFile.hxx | 119 | ||||
-rw-r--r-- | unotools/source/ucbhelper/localfilehelper.cxx | 92 | ||||
-rw-r--r-- | unotools/source/ucbhelper/progresshandlerwrap.cxx | 88 | ||||
-rw-r--r-- | unotools/source/ucbhelper/tempfile.cxx | 490 | ||||
-rw-r--r-- | unotools/source/ucbhelper/ucbhelper.cxx | 413 | ||||
-rw-r--r-- | unotools/source/ucbhelper/ucblockbytes.cxx | 1338 | ||||
-rw-r--r-- | unotools/source/ucbhelper/ucblockbytes.hxx | 139 | ||||
-rw-r--r-- | unotools/source/ucbhelper/ucbstreamhelper.cxx | 237 | ||||
-rw-r--r-- | unotools/source/ucbhelper/xtempfile.cxx | 435 |
9 files changed, 3351 insertions, 0 deletions
diff --git a/unotools/source/ucbhelper/XTempFile.hxx b/unotools/source/ucbhelper/XTempFile.hxx new file mode 100644 index 000000000..7fcaefb7b --- /dev/null +++ b/unotools/source/ucbhelper/XTempFile.hxx @@ -0,0 +1,119 @@ +/* -*- 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 . + */ +#pragma once + +#include <optional> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XTempFile.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase.hxx> +#include <mutex> +#include <unotools/tempfile.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +class SvStream; +namespace utl { class TempFile; } + + +typedef ::cppu::WeakImplHelper< css::io::XTempFile + , css::io::XInputStream + , css::io::XOutputStream + , css::io::XTruncate + , css::beans::XPropertySet + , css::beans::XFastPropertySet + , css::beans::XPropertyAccess + , css::lang::XServiceInfo> OTempFileBase; + +class OTempFileService : public OTempFileBase +{ +protected: + std::optional<utl::TempFile> mpTempFile; + std::mutex maMutex; + SvStream* mpStream; + bool mbRemoveFile; + bool mbInClosed; + bool mbOutClosed; + + void checkError () const; + void checkConnected (); + +public: + explicit OTempFileService (css::uno::Reference< css::uno::XComponentContext > const & context); + + //Methods + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XTempFile + virtual sal_Bool SAL_CALL getRemoveFile() override; + virtual void SAL_CALL setRemoveFile( sal_Bool _removefile ) override; + virtual OUString SAL_CALL getUri() override; + virtual OUString SAL_CALL getResourceName() override; + + // 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; + // XOutputStream + virtual void SAL_CALL writeBytes( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual void SAL_CALL flush( ) override; + virtual void SAL_CALL closeOutput( ) 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; + // XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override; + // XTruncate + virtual void SAL_CALL truncate() override; + + // XPropertySet + virtual ::css::uno::Reference< ::css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( const ::rtl::OUString& aPropertyName, const ::css::uno::Any& aValue ) override; + virtual ::css::uno::Any SAL_CALL getPropertyValue( const ::rtl::OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const ::rtl::OUString& aPropertyName, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const ::rtl::OUString& aPropertyName, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const ::rtl::OUString& PropertyName, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const ::rtl::OUString& PropertyName, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& aListener ) override; + // XFastPropertySet + virtual void SAL_CALL setFastPropertyValue( ::sal_Int32 nHandle, const ::css::uno::Any& aValue ) override; + virtual ::css::uno::Any SAL_CALL getFastPropertyValue( ::sal_Int32 nHandle ) override; + // XPropertyAccess + virtual ::css::uno::Sequence< ::css::beans::PropertyValue > SAL_CALL getPropertyValues() override; + virtual void SAL_CALL setPropertyValues( const ::css::uno::Sequence< ::css::beans::PropertyValue >& aProps ) override; + + + virtual ~OTempFileService () override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/localfilehelper.cxx b/unotools/source/ucbhelper/localfilehelper.cxx new file mode 100644 index 000000000..bdabd5f0a --- /dev/null +++ b/unotools/source/ucbhelper/localfilehelper.cxx @@ -0,0 +1,92 @@ +/* -*- 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/sdbc/XResultSet.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <comphelper/DirectoryHelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> +#include <unotools/localfilehelper.hxx> +#include <rtl/ustring.hxx> +#include <ucbhelper/content.hxx> +#include <vector> + +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace utl +{ + +css::uno::Sequence < OUString > LocalFileHelper::GetFolderContents( const OUString& rFolder, bool bFolder ) +{ + std::vector< OUString > vFiles; + try + { + ::ucbhelper::Content aCnt( + rFolder, Reference< XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + Reference< css::sdbc::XResultSet > xResultSet; + css::uno::Sequence< OUString > aProps { "Url" }; + + try + { + ::ucbhelper::ResultSetInclude eInclude = bFolder ? ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS : ::ucbhelper::INCLUDE_DOCUMENTS_ONLY; + xResultSet = aCnt.createCursor( aProps, eInclude ); + } + catch( css::ucb::CommandAbortedException& ) + { + } + catch( Exception& ) + { + } + + if ( xResultSet.is() ) + { + Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + try + { + while ( xResultSet->next() ) + vFiles.push_back( xContentAccess->queryContentIdentifierString() ); + } + catch( css::ucb::CommandAbortedException& ) + { + } + catch( Exception& ) + { + } + } + } + catch( Exception& ) + { + } + + return comphelper::containerToSequence(vFiles); +} + +void removeTree(OUString const & url) { + const bool bError = comphelper::DirectoryHelper::deleteDirRecursively(url); + SAL_WARN_IF(bError, "desktop.app", "error removing directory " << url); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/progresshandlerwrap.cxx b/unotools/source/ucbhelper/progresshandlerwrap.cxx new file mode 100644 index 000000000..29c1067ec --- /dev/null +++ b/unotools/source/ucbhelper/progresshandlerwrap.cxx @@ -0,0 +1,88 @@ +/* -*- 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 <unotools/progresshandlerwrap.hxx> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <utility> + +namespace utl +{ + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::ucb; + +ProgressHandlerWrap::ProgressHandlerWrap( css::uno::Reference< css::task::XStatusIndicator > xSI ) +: m_xStatusIndicator(std::move( xSI )) +{ +} + +static bool getStatusFromAny_Impl( const Any& aAny, OUString& aText, sal_Int32& nNum ) +{ + bool bNumIsSet = false; + + Sequence< Any > aSetList; + if( aAny >>= aSetList ) + for( const auto& rSet : std::as_const(aSetList) ) + { + if( !bNumIsSet && ( rSet >>= nNum ) ) + bNumIsSet = true; + else + aText.isEmpty() && ( rSet >>= aText ); + } + + return bNumIsSet; +} + +void SAL_CALL ProgressHandlerWrap::push( const Any& Status ) +{ + if( !m_xStatusIndicator.is() ) + return; + + OUString aText; + sal_Int32 nRange; + + if( getStatusFromAny_Impl( Status, aText, nRange ) ) + m_xStatusIndicator->start( aText, nRange ); +} + +void SAL_CALL ProgressHandlerWrap::update( const Any& Status ) +{ + if( !m_xStatusIndicator.is() ) + return; + + OUString aText; + sal_Int32 nValue; + + if( getStatusFromAny_Impl( Status, aText, nValue ) ) + { + if( !aText.isEmpty() ) m_xStatusIndicator->setText( aText ); + m_xStatusIndicator->setValue( nValue ); + } +} + +void SAL_CALL ProgressHandlerWrap::pop() +{ + if( m_xStatusIndicator.is() ) + m_xStatusIndicator->end(); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/tempfile.cxx b/unotools/source/ucbhelper/tempfile.cxx new file mode 100644 index 000000000..faca685d2 --- /dev/null +++ b/unotools/source/ucbhelper/tempfile.cxx @@ -0,0 +1,490 @@ +/* -*- 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 <cassert> +#include <utility> + +#include <unotools/tempfile.hxx> +#include <rtl/ustring.hxx> +#include <osl/mutex.hxx> +#include <osl/detail/file.h> +#include <osl/file.hxx> +#include <tools/time.hxx> +#include <tools/debug.hxx> +#include <comphelper/DirectoryHelper.hxx> + +#ifdef UNX +#include <unistd.h> +#elif defined( _WIN32 ) +#include <process.h> +#endif + +using namespace osl; + +namespace +{ + OUString gTempNameBase_Impl; +} + +namespace utl +{ + +static OUString getParentName( std::u16string_view aFileName ) +{ + size_t lastIndex = aFileName.rfind( '/' ); + OUString aParent; + + if (lastIndex != std::u16string_view::npos) + { + aParent = aFileName.substr(0, lastIndex); + + if (aParent.endsWith(":") && aParent.getLength() == 6) + aParent += "/"; + + if (aParent.equalsIgnoreAsciiCase("file://")) + aParent = "file:///"; + } + + return aParent; +} + +static bool ensuredir( const OUString& rUnqPath ) +{ + OUString aPath; + if ( rUnqPath.isEmpty() ) + return false; + + // remove trailing slash + if ( rUnqPath.endsWith("/") ) + aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 ); + else + aPath = rUnqPath; + + // HACK: create directory on a mount point with nobrowse option + // returns ENOSYS in any case !! + osl::Directory aDirectory( aPath ); + osl::FileBase::RC nError = aDirectory.open(); + aDirectory.close(); + if( nError == osl::File::E_None ) + return true; + + // try to create the directory + nError = osl::Directory::create( aPath ); + bool bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST ); + if( !bSuccess ) + { + // perhaps parent(s) don't exist + OUString aParentDir = getParentName( aPath ); + if ( aParentDir != aPath ) + { + bSuccess = ensuredir( getParentName( aPath ) ); + + // After parent directory structure exists try it one's more + if ( bSuccess ) + { + // Parent directory exists, retry creation of directory + nError = osl::Directory::create( aPath ); + bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST ); + } + } + } + + return bSuccess; +} + +static OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs ) +{ + OUString aName; + + // Ignore pParent on iOS. We don't want to create any temp files + // in the same directory where the document being edited is. +#ifndef IOS + if ( pParent && !pParent->isEmpty() ) + { + // test for valid filename + OUString aRet; + if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet) + == osl::FileBase::E_None) + && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet) + == osl::FileBase::E_None)) + { + ::osl::DirectoryItem aItem; + sal_Int32 i = aRet.getLength(); + if ( aRet[i-1] == '/' ) + i--; + + if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None || bCreateParentDirs ) + aName = aRet; + } + } +#else + (void) pParent; + (void) bCreateParentDirs; +#endif + + if ( aName.isEmpty() ) + { + if (gTempNameBase_Impl.isEmpty()) + { + OUString ustrTempDirURL; + ::osl::FileBase::RC rc = ::osl::File::getTempDirURL( + ustrTempDirURL ); + if (rc == ::osl::FileBase::E_None) + gTempNameBase_Impl = ustrTempDirURL; + ensuredir( aName ); + } + // if no parent or invalid parent : use default directory + DBG_ASSERT( !gTempNameBase_Impl.isEmpty(), "No TempDir!" ); + aName = gTempNameBase_Impl; + } + + // Make sure that directory ends with a separator + if( !aName.isEmpty() && !aName.endsWith("/") ) + aName += "/"; + + return aName; +} + +namespace { + +class Tokens { +public: + virtual bool next(OUString *) = 0; + +protected: + virtual ~Tokens() {} // avoid warnings +}; + +class SequentialTokens: public Tokens { +public: + explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {} + + bool next(OUString * token) override { + assert(token != nullptr); + if (m_value == SAL_MAX_UINT32) { + return false; + } + *token = m_show ? OUString::number(m_value) : OUString(); + ++m_value; + m_show = true; + return true; + } + +private: + sal_uInt32 m_value; + bool m_show; +}; + +class UniqueTokens: public Tokens { +public: + UniqueTokens(): m_count(0) {} + + bool next(OUString * token) override { + assert(token != nullptr); + // Because of the shared globalValue, no single instance of UniqueTokens + // is guaranteed to exhaustively test all 36^6 possible values, but stop + // after that many attempts anyway: + sal_uInt32 radix = 36; + sal_uInt32 max = radix * radix * radix * radix * radix * radix; + // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295 + if (m_count == max) { + return false; + } + sal_uInt32 v; + { + osl::MutexGuard g(osl::Mutex::getGlobalMutex()); + globalValue + = ((globalValue == SAL_MAX_UINT32 + ? tools::Time::GetSystemTicks() : globalValue + 1) + % max); + v = globalValue; + } + *token = OUString::number(v, radix); + ++m_count; + return true; + } + +private: + static sal_uInt32 globalValue; + + sal_uInt32 m_count; +}; + +} + +sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32; + +namespace +{ + class TempDirCreatedObserver : public DirectoryCreationObserver + { + public: + virtual void DirectoryCreated(const OUString& aDirectoryUrl) override + { + File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead | + osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe ); + }; + }; +}; + +static OUString lcl_createName( + std::u16string_view rLeadingChars, Tokens & tokens, const OUString* pExtension, + const OUString* pParent, bool bDirectory, bool bKeep, bool bLock, + bool bCreateParentDirs ) +{ + OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs ); + if ( bCreateParentDirs ) + { + size_t nOffset = rLeadingChars.rfind(u"/"); + OUString aDirName; + if (std::u16string_view::npos != nOffset) + aDirName = aName + rLeadingChars.substr( 0, nOffset ); + else + aDirName = aName; + TempDirCreatedObserver observer; + FileBase::RC err = Directory::createPath( aDirName, &observer ); + if ( err != FileBase::E_None && err != FileBase::E_EXIST ) + return OUString(); + } + aName += rLeadingChars; + + OUString token; + while (tokens.next(&token)) + { + OUString aTmp( aName + token ); + if ( pExtension ) + aTmp += *pExtension; + else + aTmp += ".tmp"; + if ( bDirectory ) + { + FileBase::RC err = Directory::create( + aTmp, + (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write + | osl_File_OpenFlag_Private)); + if ( err == FileBase::E_None ) + { + // !bKeep: only for creating a name, not a file or directory + if ( bKeep || Directory::remove( aTmp ) == FileBase::E_None ) + return aTmp; + else + return OUString(); + } + else if ( err != FileBase::E_EXIST ) + // if f.e. name contains invalid chars stop trying to create dirs + return OUString(); + } + else + { + DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" ); + File aFile( aTmp ); + FileBase::RC err = aFile.open( + osl_File_OpenFlag_Create | osl_File_OpenFlag_Private + | (bLock ? 0 : osl_File_OpenFlag_NoLock)); + if ( err == FileBase::E_None || (bLock && err == FileBase::E_NOLCK) ) + { + aFile.close(); + return aTmp; + } + else if ( err != FileBase::E_EXIST ) + { + // if f.e. name contains invalid chars stop trying to create dirs + // but if there is a folder with such name proceed further + + DirectoryItem aTmpItem; + FileStatus aTmpStatus( osl_FileStatus_Mask_Type ); + if ( DirectoryItem::get( aTmp, aTmpItem ) != FileBase::E_None + || aTmpItem.getFileStatus( aTmpStatus ) != FileBase::E_None + || aTmpStatus.getFileType() != FileStatus::Directory ) + return OUString(); + } + } + } + return OUString(); +} + +static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true ) +{ + OUString aEyeCatcher = "lu"; +#ifdef UNX +#ifdef DBG_UTIL + const char* eye = getenv("LO_TESTNAME"); + if(eye) + { + aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US); + } +#else + static const pid_t pid = getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif +#elif defined(_WIN32) + static const int pid = _getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif + UniqueTokens t; + return lcl_createName( aEyeCatcher, t, nullptr, pParent, bDir, bKeep, + false, false); +} + +OUString TempFile::CreateTempName() +{ + OUString aName(CreateTempName_Impl( nullptr, false )); + + // convert to file URL + OUString aTmp; + if ( !aName.isEmpty() ) + FileBase::getSystemPathFromFileURL( aName, aTmp ); + return aTmp; +} + +TempFile::TempFile( const OUString* pParent, bool bDirectory ) + : bIsDirectory( bDirectory ) + , bKillingFileEnabled( false ) +{ + aName = CreateTempName_Impl( pParent, true, bDirectory ); +} + +TempFile::TempFile( std::u16string_view rLeadingChars, bool _bStartWithZero, + const OUString* pExtension, const OUString* pParent, + bool bCreateParentDirs ) + : bIsDirectory( false ) + , bKillingFileEnabled( false ) +{ + SequentialTokens t(_bStartWithZero); + aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false, + true, true, bCreateParentDirs ); +} + +TempFile::TempFile(TempFile && other) noexcept : + aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory), + bKillingFileEnabled(other.bKillingFileEnabled) +{ + other.bKillingFileEnabled = false; +} + +TempFile::~TempFile() +{ + if ( !bKillingFileEnabled ) + return; + + pStream.reset(); + if ( bIsDirectory ) + { + comphelper::DirectoryHelper::deleteDirRecursively(aName); + } + else + { + File::remove( aName ); + } +} + +bool TempFile::IsValid() const +{ + return !aName.isEmpty(); +} + +OUString TempFile::GetFileName() const +{ + OUString aTmp; + FileBase::getSystemPathFromFileURL(aName, aTmp); + return aTmp; +} + +OUString const & TempFile::GetURL() const +{ + // if you request the URL, then you presumably want to access this via UCB, + // and UCB will want to open the file via a separate file handle, which means + // we have to make this file data actually hit disk. We do this here (and not + // elsewhere) to make the other (normal) paths fast. Flushing to disk + // really slows temp files down. + if (pStream) + pStream->Flush(); + return aName; +} + +SvStream* TempFile::GetStream( StreamMode eMode ) +{ + if (!pStream) + { + if (!aName.isEmpty()) + pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY)); + else + pStream.reset(new SvMemoryStream); + } + + return pStream.get(); +} + +void TempFile::CloseStream() +{ + pStream.reset(); +} + +OUString TempFile::SetTempNameBaseDirectory( const OUString &rBaseName ) +{ + if( rBaseName.isEmpty() ) + return OUString(); + + OUString aUnqPath( rBaseName ); + + // remove trailing slash + if ( rBaseName.endsWith("/") ) + aUnqPath = rBaseName.copy( 0, rBaseName.getLength() - 1 ); + + // try to create the directory + bool bRet = false; + osl::FileBase::RC err = osl::Directory::create( aUnqPath ); + if ( err != FileBase::E_None && err != FileBase::E_EXIST ) + // perhaps parent(s) don't exist + bRet = ensuredir( aUnqPath ); + else + bRet = true; + + // failure to create base directory means returning an empty string + OUString aTmp; + if ( bRet ) + { + // append own internal directory + OUString &rTempNameBase_Impl = gTempNameBase_Impl; + rTempNameBase_Impl = rBaseName + "/"; + + TempFile aBase( nullptr, true ); + if ( aBase.IsValid() ) + // use it in case of success + rTempNameBase_Impl = aBase.aName; + + // return system path of used directory + FileBase::getSystemPathFromFileURL( rTempNameBase_Impl, aTmp ); + } + + return aTmp; +} + +OUString TempFile::GetTempNameBaseDirectory() +{ + return ConstructTempDir_Impl(nullptr, false); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucbhelper.cxx b/unotools/source/ucbhelper/ucbhelper.cxx new file mode 100644 index 000000000..4878ec1ce --- /dev/null +++ b/unotools/source/ucbhelper/ucbhelper.cxx @@ -0,0 +1,413 @@ +/* -*- 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 <cassert> +#include <vector> + +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/IOErrorCode.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/XUniversalContentBroker.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/simplefileaccessinteraction.hxx> +#include <osl/file.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/datetime.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> +#include <ucbhelper/commandenvironment.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> + +namespace com::sun::star::ucb { class XProgressHandler; } +namespace com::sun::star::uno { class XComponentContext; } +namespace com::sun::star::util { struct DateTime; } + +namespace { + +OUString canonic(OUString const & url) { + INetURLObject o(url); + SAL_WARN_IF(o.HasError(), "unotools.ucbhelper", "Invalid URL \"" << url << '"'); + return o.GetMainURL(INetURLObject::DecodeMechanism::NONE); +} + +ucbhelper::Content content(OUString const & url) { + return ucbhelper::Content( + canonic(url), + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext()); +} + +ucbhelper::Content content(INetURLObject const & url) { + return ucbhelper::Content( + url.GetMainURL(INetURLObject::DecodeMechanism::NONE), + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext()); +} + +std::vector<OUString> getContents(OUString const & url) { + try { + std::vector<OUString> cs; + ucbhelper::Content c(content(url)); + css::uno::Sequence<OUString> args { "Title" }; + css::uno::Reference<css::sdbc::XResultSet> res( c.createCursor(args), css::uno::UNO_SET_THROW); + css::uno::Reference<css::ucb::XContentAccess> acc( res, css::uno::UNO_QUERY_THROW); + while (res->next()) { + cs.push_back(acc->queryContentIdentifierString()); + } + return cs; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "getContents(" << url << ")"); + return std::vector<OUString>(); + } +} + +OUString getCasePreservingUrl(const INetURLObject& url) { + return + content(url).executeCommand( + "getCasePreservingURL", + css::uno::Any()). + get<OUString>(); +} + +DateTime convert(css::util::DateTime const & dt) { + return DateTime(dt); +} + +} + +css::uno::Reference< css::ucb::XCommandEnvironment > utl::UCBContentHelper::getDefaultCommandEnvironment() +{ + css::uno::Reference< css::task::XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent( + comphelper::getProcessComponentContext(), nullptr ) ); + + css::uno::Reference< css::ucb::XProgressHandler > xProgress; + rtl::Reference<ucbhelper::CommandEnvironment> pCommandEnv = + new ::ucbhelper::CommandEnvironment( + new comphelper::SimpleFileAccessInteraction( xIH ), xProgress ); + + return pCommandEnv; +} + +bool utl::UCBContentHelper::IsDocument(OUString const & url) { + try { + return content(url).isDocument(); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsDocument(" << url << ")"); + return false; + } +} + +css::uno::Any utl::UCBContentHelper::GetProperty( + OUString const & url, OUString const & property) +{ + try { + return content(url).getPropertyValue(property); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetProperty(" << url << ", " << property << ")"); + return css::uno::Any(); + } +} + +bool utl::UCBContentHelper::IsFolder(OUString const & url) { + try { + return content(url).isFolder(); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsFolder(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::GetTitle( + OUString const & url, OUString * title) +{ + assert(title != nullptr); + try { + return content(url).getPropertyValue("Title") >>= *title; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetTitle(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::Kill(OUString const & url) { + try { + content(url).executeCommand( + "delete", + css::uno::Any(true)); + return true; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::Kill(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::MakeFolder( + ucbhelper::Content & parent, OUString const & title, + ucbhelper::Content & result) +{ + bool exists = false; + try { + const css::uno::Sequence<css::ucb::ContentInfo> info( + parent.queryCreatableContentsInfo()); + for (const auto& rInfo : info) { + // Simply look for the first KIND_FOLDER: + if ((rInfo.Attributes + & css::ucb::ContentInfoAttribute::KIND_FOLDER) + != 0) + { + // Make sure the only required bootstrap property is "Title": + if ( rInfo.Properties.getLength() != 1 || rInfo.Properties[0].Name != "Title" ) + { + continue; + } + if (parent.insertNewContent(rInfo.Type, { "Title" }, { css::uno::Any(title) }, result)) + { + return true; + } + } + } + } catch (css::ucb::InteractiveIOException const & e) { + if (e.Code == css::ucb::IOErrorCode_ALREADY_EXISTING) { + exists = true; + } else { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::MakeFolder(" << title << ")"); + } + } catch (css::ucb::NameClashException const &) { + exists = true; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::MakeFolder(" << title << ") "); + } + if (exists) { + INetURLObject o(parent.getURL()); + o.Append(title); + result = content(o); + return true; + } else { + return false; + } +} + +bool utl::UCBContentHelper::IsYounger( + OUString const & younger, OUString const & older) +{ + try { + return + convert( + content(younger).getPropertyValue( + "DateModified"). + get<css::util::DateTime>()) + > convert( + content(older).getPropertyValue( + "DateModified"). + get<css::util::DateTime>()); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::IsYounger(" << younger << ", " << older << ")"); + return false; + } +} + +bool utl::UCBContentHelper::Exists(OUString const & url) { + OUString pathname; + if (osl::FileBase::getSystemPathFromFileURL(url, pathname) + == osl::FileBase::E_None) + { + // Try to create a directory entry for the given URL: + OUString url2; + if (osl::FileBase::getFileURLFromSystemPath(pathname, url2) + == osl::FileBase::E_None) + { + // #106526 osl_getDirectoryItem is an existence check, no further + // osl_getFileStatus call necessary: + osl::DirectoryItem item; + return osl::DirectoryItem::get(url2, item) == osl::FileBase::E_None; + } else { + return false; + } + } else { + // Divide URL into folder and name part: + INetURLObject o(url); + OUString name( + o.getName( + INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset)); + o.removeSegment(); + o.removeFinalSlash(); + std::vector<OUString> cs( + getContents(o.GetMainURL(INetURLObject::DecodeMechanism::NONE))); + return std::any_of(cs.begin(), cs.end(), + [&name](const OUString& rItem) { + return INetURLObject(rItem). + getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset). + equalsIgnoreAsciiCase(name); }); + } +} + +bool utl::UCBContentHelper::IsSubPath( + OUString const & parent, OUString const & child) +{ + // The comparison is done in the following way: + // - First, compare case sensitively + // - If names are different, try a fallback comparing case insensitively + // - If the last comparison succeeded, get case preserving normalized names + // for the files and compare them + // (The second step is required because retrieving the normalized names + // might be very expensive in some cases.) + INetURLObject candidate(child); + INetURLObject folder(parent); + if (candidate.GetProtocol() != folder.GetProtocol()) { + return false; + } + INetURLObject candidateLower(child.toAsciiLowerCase()); + INetURLObject folderLower(parent.toAsciiLowerCase()); + try { + INetURLObject tmp; + do { + if (candidate == folder + || (candidate.GetProtocol() == INetProtocol::File + && candidateLower == folderLower + && (getCasePreservingUrl(candidate) + == getCasePreservingUrl(folder)))) + { + return true; + } + tmp = candidate; + } while (candidate.removeSegment() && candidateLower.removeSegment() + && candidate != tmp); + // INetURLObject::removeSegment sometimes returns true without + // modifying the URL, e.g., in case of "file:///" + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::IsSubPath(" << parent << ", " << child << ")"); + } + return false; +} + +bool utl::UCBContentHelper::EqualURLs( + OUString const & url1, OUString const & url2) +{ + if (url1.isEmpty() || url2.isEmpty()) { + return false; + } + css::uno::Reference< css::ucb::XUniversalContentBroker > ucb( + css::ucb::UniversalContentBroker::create( + comphelper::getProcessComponentContext())); + return + ucb->compareContentIds( + ucb->createContentIdentifier(canonic(url1)), + ucb->createContentIdentifier(canonic(url2))) + == 0; +} + +bool utl::UCBContentHelper::ensureFolder( + const css::uno::Reference< css::uno::XComponentContext >& xCtx, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + std::u16string_view rFolder, ucbhelper::Content & result) noexcept +{ + try + { + INetURLObject aURL( rFolder ); + OUString aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + aURL.removeSegment(); + ::ucbhelper::Content aParent; + + if ( ::ucbhelper::Content::create( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + xEnv, xCtx, aParent ) ) + { + return ::utl::UCBContentHelper::MakeFolder(aParent, aTitle, result); + } + } + catch (...) + { + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucblockbytes.cxx b/unotools/source/ucbhelper/ucblockbytes.cxx new file mode 100644 index 000000000..ad116eff2 --- /dev/null +++ b/unotools/source/ucbhelper/ucblockbytes.cxx @@ -0,0 +1,1338 @@ +/* -*- 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 "ucblockbytes.hxx" + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <salhelper/condition.hxx> +#include <osl/thread.hxx> +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <tools/solar.h> +#include <ucbhelper/interactionrequest.hxx> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/ucb/XCommandProcessor.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertiesChangeNotifier.hpp> +#include <com/sun/star/beans/XPropertiesChangeListener.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XActiveDataControl.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <cppuhelper/implbase.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <comphelper/bytereader.hxx> +#include <comphelper/storagehelper.hxx> +#include <ucbhelper/content.hxx> +#include <mutex> +#include <utility> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +namespace utl +{ + +namespace { + +/** + Helper class for getting a XInputStream when opening a content + */ +class UcbDataSink_Impl : public ::cppu::WeakImplHelper< XActiveDataControl, XActiveDataSink > +{ + UcbLockBytesRef m_xLockBytes; + +public: + explicit UcbDataSink_Impl( UcbLockBytes* pLockBytes ) + : m_xLockBytes( pLockBytes ) + {} + + // XActiveDataControl. + virtual void SAL_CALL addListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL removeListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL start() override {} + virtual void SAL_CALL terminate() override + { m_xLockBytes->terminate(); } + + // XActiveDataSink. + virtual void SAL_CALL setInputStream ( const Reference<XInputStream> &rxInputStream) override + { m_xLockBytes->setInputStream(rxInputStream); } + virtual Reference<XInputStream> SAL_CALL getInputStream() override + { return m_xLockBytes->getInputStream(); } +}; + +/** + Helper class for getting a XStream when opening a content + */ +class UcbStreamer_Impl : public ::cppu::WeakImplHelper< XActiveDataStreamer, XActiveDataControl > +{ + Reference < XStream > m_xStream; + UcbLockBytesRef m_xLockBytes; + +public: + explicit UcbStreamer_Impl( UcbLockBytes* pLockBytes ) + : m_xLockBytes( pLockBytes ) + {} + + // XActiveDataControl. + virtual void SAL_CALL addListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL removeListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL start() override {} + virtual void SAL_CALL terminate() override + { m_xLockBytes->terminate(); } + + // XActiveDataStreamer + virtual void SAL_CALL setStream( const Reference< XStream >& aStream ) override + { m_xStream = aStream; m_xLockBytes->setStream( aStream ); } + virtual Reference< XStream > SAL_CALL getStream() override + { return m_xStream; } +}; + +/** + Helper class for managing interactions and progress when executing UCB commands + */ +class UcbTaskEnvironment : public ::cppu::WeakImplHelper< XCommandEnvironment > +{ + Reference< XInteractionHandler > m_xInteractionHandler; + Reference< XProgressHandler > m_xProgressHandler; + +public: + UcbTaskEnvironment( const Reference< XInteractionHandler>& rxInteractionHandler, + const Reference< XProgressHandler>& rxProgressHandler ) + : m_xInteractionHandler( rxInteractionHandler ) + , m_xProgressHandler( rxProgressHandler ) + {} + + virtual Reference<XInteractionHandler> SAL_CALL getInteractionHandler() override + { return m_xInteractionHandler; } + + virtual Reference<XProgressHandler> SAL_CALL getProgressHandler() override + { return m_xProgressHandler; } +}; + +/** + Helper class for property change notifies when executing UCB commands +*/ +class UcbPropertiesChangeListener_Impl : public ::cppu::WeakImplHelper< XPropertiesChangeListener > +{ +public: + UcbLockBytesRef m_xLockBytes; + + explicit UcbPropertiesChangeListener_Impl( UcbLockBytesRef xRef ) + : m_xLockBytes(std::move( xRef )) + {} + + virtual void SAL_CALL disposing ( const EventObject &/*rEvent*/) override {} + virtual void SAL_CALL propertiesChange ( const Sequence<PropertyChangeEvent> &rEvent) override; +}; + +} + +void SAL_CALL UcbPropertiesChangeListener_Impl::propertiesChange ( const Sequence<PropertyChangeEvent> &rEvent) +{ + for (const auto& rPropChangeEvent : rEvent) + { + if (rPropChangeEvent.PropertyName == "DocumentHeader") + { + m_xLockBytes->SetStreamValid(); + } + } +} + +namespace { + +class Moderator + : public osl::Thread +{ + // usage restriction: + // It might be possible, that the call to the interactionhandler and/or + // progresshandler is done asynchronously, while the 'execute' simply + // returns. This would imply that these class must be refcounted!!! + +public: + /// @throws ContentCreationException + /// @throws RuntimeException + Moderator( + Reference < XContent > const & xContent, + Reference < XInteractionHandler > const & xInteract, + Command aArg + ); + + enum class ResultType { + NORESULT, + + INTERACTIONREQUEST, // reply expected + + INPUTSTREAM, + STREAM, + + RESULT, + TIMEDOUT, + COMMANDABORTED, + COMMANDFAILED, + INTERACTIVEIO, + UNSUPPORTED, + GENERAL + }; + + class ConditionRes + : public salhelper::Condition + { + public: + ConditionRes(osl::Mutex& aMutex,Moderator& aModerator) + : salhelper::Condition(aMutex), + m_aModerator(aModerator) + { + } + + protected: + bool applies() const override { + return m_aModerator.m_aResultType != ResultType::NORESULT; + } + + private: + Moderator& m_aModerator; + }; + + struct Result { + ResultType type; + Any result; + IOErrorCode ioErrorCode; + }; + + Result getResult(const sal_uInt32 milliSec); + + enum ReplyType { + NOREPLY, + EXIT, + REQUESTHANDLED + }; + + class ConditionRep + : public salhelper::Condition + { + public: + ConditionRep(osl::Mutex& aMutex,Moderator& aModerator) + : salhelper::Condition(aMutex), + m_aModerator(aModerator) + { + } + + protected: + bool applies() const override { + return m_aModerator.m_aReplyType != NOREPLY; + } + + private: + Moderator& m_aModerator; + }; + + void setReply(ReplyType); + + void handle( const Reference<XInteractionRequest >& Request ); + + void setStream(const Reference< XStream >& aStream); + void setInputStream(const Reference<XInputStream> &rxInputStream); + +protected: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + +private: + osl::Mutex m_aMutex; + + friend class ConditionRes; + + ConditionRes m_aRes; + ResultType m_aResultType; + IOErrorCode m_nIOErrorCode; + Any m_aResult; + + friend class ConditionRep; + + ConditionRep m_aRep; + ReplyType m_aReplyType; + + Command m_aArg; + ::ucbhelper::Content m_aContent; +}; + +class ModeratorsActiveDataStreamer + : public ::cppu::WeakImplHelper<XActiveDataStreamer> +{ +public: + + explicit ModeratorsActiveDataStreamer(Moderator &theModerator); + + // XActiveDataStreamer + virtual void SAL_CALL + setStream( + const Reference< XStream >& aStream + ) override; + + virtual Reference<XStream> SAL_CALL getStream () override + { + std::scoped_lock aGuard(m_aMutex); + return m_xStream; + } + +private: + Moderator& m_aModerator; + + std::mutex m_aMutex; + Reference<XStream> m_xStream; +}; + +class ModeratorsActiveDataSink + : public ::cppu::WeakImplHelper<XActiveDataSink> +{ +public: + + explicit ModeratorsActiveDataSink(Moderator &theModerator); + + // XActiveDataSink. + virtual void SAL_CALL + setInputStream ( + const Reference<XInputStream> &rxInputStream + ) override; + + virtual Reference<XInputStream> SAL_CALL getInputStream() override + { + std::scoped_lock aGuard(m_aMutex); + return m_xStream; + } + +private: + Moderator& m_aModerator; + std::mutex m_aMutex; + Reference<XInputStream> m_xStream; +}; + +} + +ModeratorsActiveDataSink::ModeratorsActiveDataSink(Moderator &theModerator) + : m_aModerator(theModerator) +{ +} + +// XActiveDataSink. +void SAL_CALL +ModeratorsActiveDataSink::setInputStream ( + const Reference<XInputStream> &rxInputStream +) +{ + m_aModerator.setInputStream(rxInputStream); + std::scoped_lock aGuard(m_aMutex); + m_xStream = rxInputStream; +} + +ModeratorsActiveDataStreamer::ModeratorsActiveDataStreamer( + Moderator &theModerator +) + : m_aModerator(theModerator) +{ +} + +// XActiveDataStreamer. +void SAL_CALL +ModeratorsActiveDataStreamer::setStream ( + const Reference<XStream> &rxStream +) +{ + m_aModerator.setStream(rxStream); + std::scoped_lock aGuard(m_aMutex); + m_xStream = rxStream; +} + +namespace { + +class ModeratorsInteractionHandler + : public ::cppu::WeakImplHelper<XInteractionHandler> +{ +public: + + explicit ModeratorsInteractionHandler(Moderator &theModerator); + + virtual void SAL_CALL + handle( const Reference<XInteractionRequest >& Request ) override; + +private: + + Moderator& m_aModerator; +}; + +} + +ModeratorsInteractionHandler::ModeratorsInteractionHandler( + Moderator &aModerator) + : m_aModerator(aModerator) +{ +} + +void SAL_CALL +ModeratorsInteractionHandler::handle( + const Reference<XInteractionRequest >& Request +) +{ + // wakes up the mainthread + m_aModerator.handle(Request); +} + +Moderator::Moderator( + Reference < XContent > const & xContent, + Reference < XInteractionHandler > const & xInteract, + Command aArg +) + : m_aRes(m_aMutex,*this), + m_aResultType(ResultType::NORESULT), + m_nIOErrorCode(IOErrorCode_ABORT), + m_aRep(m_aMutex,*this), + m_aReplyType(NOREPLY), + m_aArg(std::move(aArg)), + m_aContent( + xContent, + new UcbTaskEnvironment( + xInteract.is() ? new ModeratorsInteractionHandler(*this) : nullptr, + nullptr), + comphelper::getProcessComponentContext()) +{ + // now exchange the whole data sink stuff + // with a thread safe version + + Reference<XInterface> *pxSink = nullptr; + + PostCommandArgument2 aPostArg; + OpenCommandArgument2 aOpenArg; + + int dec(2); + if(m_aArg.Argument >>= aPostArg) { + pxSink = &aPostArg.Sink; + dec = 0; + } + else if(m_aArg.Argument >>= aOpenArg) { + pxSink = &aOpenArg.Sink; + dec = 1; + } + + if(dec ==2) + throw ContentCreationException(); + + Reference < XActiveDataSink > xActiveSink(*pxSink,UNO_QUERY); + if(xActiveSink.is()) + pxSink->set( static_cast<cppu::OWeakObject*>(new ModeratorsActiveDataSink(*this))); + + Reference<XActiveDataStreamer> xStreamer( *pxSink, UNO_QUERY ); + if ( xStreamer.is() ) + pxSink->set( static_cast<cppu::OWeakObject*>(new ModeratorsActiveDataStreamer(*this))); + + if(dec == 0) + m_aArg.Argument <<= aPostArg; + else if(dec == 1) + m_aArg.Argument <<= aOpenArg; +} + +Moderator::Result Moderator::getResult(const sal_uInt32 milliSec) +{ + Result ret; + try { + salhelper::ConditionWaiter aWaiter(m_aRes,milliSec); + ret.type = m_aResultType; + ret.result = m_aResult; + ret.ioErrorCode = m_nIOErrorCode; + + // reset + m_aResultType = ResultType::NORESULT; + } + catch (const salhelper::ConditionWaiter::timedout&) + { + ret.type = ResultType::TIMEDOUT; + } + + return ret; +} + +void Moderator::setReply(ReplyType aReplyType ) +{ + salhelper::ConditionModifier aMod(m_aRep); + m_aReplyType = aReplyType; +} + +void Moderator::handle( const Reference<XInteractionRequest >& Request ) +{ + ReplyType aReplyType; + + do { + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::INTERACTIONREQUEST; + m_aResult <<= Request; + } + + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + + // reset + m_aReplyType = NOREPLY; + } + + if(aReplyType == EXIT) { + const Sequence<Reference<XInteractionContinuation> > aSeq( + Request->getContinuations()); + for(const auto& rContinuation : aSeq) { + Reference<XInteractionAbort> aRef(rContinuation,UNO_QUERY); + if(aRef.is()) { + aRef->select(); + } + } + + // resignal the exit condition + setReply(EXIT); + break; + } + } while(aReplyType != REQUESTHANDLED); +} + +void Moderator::setStream(const Reference< XStream >& aStream) +{ + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::STREAM; + m_aResult <<= aStream; + } + ReplyType aReplyType; + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + m_aReplyType = NOREPLY; + } + if(aReplyType == EXIT) + setReply(EXIT); +} + +void Moderator::setInputStream(const Reference<XInputStream> &rxInputStream) +{ + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::INPUTSTREAM; + m_aResult <<= rxInputStream; + } + ReplyType aReplyType; + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + m_aReplyType = NOREPLY; + } + if(aReplyType == EXIT) + setReply(EXIT); +} + +void SAL_CALL Moderator::run() +{ + osl_setThreadName("utl::Moderator"); + + ResultType aResultType; + Any aResult; + IOErrorCode nIOErrorCode = IOErrorCode_ABORT; + + try + { + aResult = m_aContent.executeCommand(m_aArg.Name,m_aArg.Argument); + aResultType = ResultType::RESULT; + } + catch (const CommandAbortedException&) + { + aResultType = ResultType::COMMANDABORTED; + } + catch (const CommandFailedException&) + { + aResultType = ResultType::COMMANDFAILED; + } + catch (const InteractiveIOException& r) + { + nIOErrorCode = r.Code; + aResultType = ResultType::INTERACTIVEIO; + } + catch (const UnsupportedDataSinkException &) + { + aResultType = ResultType::UNSUPPORTED; + } + catch (const Exception&) + { + aResultType = ResultType::GENERAL; + } + + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = aResultType; + m_aResult = aResult; + m_nIOErrorCode = nIOErrorCode; + } +} + +void SAL_CALL Moderator::onTerminated() +{ + { + salhelper::ConditionWaiter aWaiter(m_aRep); + } + delete this; +} + +/** + Function for opening UCB contents synchronously, + but with handled timeout; +*/ +static bool UCBOpenContentSync_( + const UcbLockBytesRef& xLockBytes, + const Reference < XContent >& xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + const Reference < XInteractionHandler >& xInteract ); + +static bool UCBOpenContentSync( + const UcbLockBytesRef& xLockBytes, + Reference < XContent > const & xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + Reference < XInteractionHandler > const & xInteract ) +{ + // http protocol must be handled in a special way: + // during the opening process the input stream may change + // only the last inputstream after notifying the document + // headers is valid + + Reference<XContentIdentifier> xContId( + xContent.is() ? xContent->getIdentifier() : nullptr ); + + OUString aScheme; + if(xContId.is()) + aScheme = xContId->getContentProviderScheme(); + + // now determine whether we use a timeout or not; + if( ! aScheme.equalsIgnoreAsciiCase("http") && + ! aScheme.equalsIgnoreAsciiCase("https") && + ! aScheme.equalsIgnoreAsciiCase("vnd.sun.star.webdav") && + ! aScheme.equalsIgnoreAsciiCase("vnd.sun.star.webdavs") && + ! aScheme.equalsIgnoreAsciiCase("ftp")) + return UCBOpenContentSync_( + xLockBytes,xContent,rArg,xSink,xInteract); + + if ( !aScheme.equalsIgnoreAsciiCase( "http" ) && + !aScheme.equalsIgnoreAsciiCase( "https" ) ) + xLockBytes->SetStreamValid(); + + Reference< XPropertiesChangeListener > xListener; + Reference< XPropertiesChangeNotifier > xProps(xContent,UNO_QUERY); + if(xProps.is()) { + xListener = + new UcbPropertiesChangeListener_Impl(xLockBytes); + xProps->addPropertiesChangeListener( + Sequence< OUString >(), + xListener); + } + + bool bException(false); + bool bAborted(false); + bool bResultAchieved(false); + + Moderator* pMod = nullptr; + try + { + pMod = new Moderator(xContent,xInteract,rArg); + pMod->create(); + //TODO: a protocol is missing how to join with the launched thread before exit(3), to + // ensure the thread is no longer relying on any infrastructure while that + // infrastructure is being shut down in atexit handlers + } + catch (const ContentCreationException&) + { + bResultAchieved = bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + sal_uInt32 nTimeout(5000); // initially 5000 milliSec + while(!bResultAchieved) { + + // try to get the result for with timeout + Moderator::Result res = pMod->getResult(nTimeout); + + switch(res.type) { + case Moderator::ResultType::STREAM: + { + Reference<XStream> result; + if(res.result >>= result) { + Reference < XActiveDataStreamer > xStreamer( + xSink, UNO_QUERY + ); + + if(xStreamer.is()) + xStreamer->setStream(result); + } + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::INPUTSTREAM: + { + Reference<XInputStream> result; + res.result >>= result; + Reference < XActiveDataSink > xActiveSink( + xSink, UNO_QUERY + ); + + if(xActiveSink.is()) + xActiveSink->setInputStream(result); + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::TIMEDOUT: + { + Reference<XInteractionRetry> xRet; + if(xInteract.is()) { + InteractiveNetworkConnectException aExcep; + INetURLObject aURL( + xContId.is() ? + xContId->getContentIdentifier() : + OUString() ); + aExcep.Server = aURL.GetHost(); + aExcep.Classification = InteractionClassification_ERROR; + aExcep.Message = "server not responding after five seconds"; + Any request; + request <<= aExcep; + rtl::Reference<ucbhelper::InteractionRequest> xIR = + new ucbhelper::InteractionRequest(request); + rtl::Reference<ucbhelper::InteractionRetry> retryP = + new ucbhelper::InteractionRetry(xIR.get()); + rtl::Reference<ucbhelper::InteractionAbort> abortP = + new ucbhelper::InteractionAbort(xIR.get()); + Sequence<Reference<XInteractionContinuation> > aSeq { retryP, abortP }; + + xIR->setContinuations(aSeq); + xInteract->handle(xIR); + rtl::Reference< ucbhelper::InteractionContinuation > ref + = xIR->getSelection(); + if(ref.is()) { + Reference<XInterface> xInt(ref); + xRet.set(xInt,UNO_QUERY); + } + } + + if(!xRet.is()) { + bAborted = true; + xLockBytes->SetError(ERRCODE_ABORT); + } + + break; + } + case Moderator::ResultType::INTERACTIONREQUEST: + { + Reference<XInteractionRequest> Request; + res.result >>= Request; + xInteract->handle(Request); + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::RESULT: + { + bResultAchieved = true; + break; + } + case Moderator::ResultType::COMMANDABORTED: + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + break; + } + case Moderator::ResultType::COMMANDFAILED: + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + break; + } + case Moderator::ResultType::INTERACTIVEIO: + { + bException = true; + if ( res.ioErrorCode == IOErrorCode_ACCESS_DENIED || + res.ioErrorCode == IOErrorCode_LOCKING_VIOLATION ) + xLockBytes->SetError( ERRCODE_IO_ACCESSDENIED ); + else if ( res.ioErrorCode == IOErrorCode_NOT_EXISTING ) + xLockBytes->SetError( ERRCODE_IO_NOTEXISTS ); + else if ( res.ioErrorCode == IOErrorCode_CANT_READ ) + xLockBytes->SetError( ERRCODE_IO_CANTREAD ); + else + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + break; + } + case Moderator::ResultType::UNSUPPORTED: + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_NOTSUPPORTED ); + break; + } + default: + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + break; + } + } + + bResultAchieved |= bException; + bResultAchieved |= bAborted; + if(nTimeout == 5000) nTimeout *= 2; + } + + if(pMod) pMod->setReply(Moderator::EXIT); + + if ( bAborted || bException ) + { + Reference < XActiveDataSink > xActiveSink( xSink, UNO_QUERY ); + if ( xActiveSink.is() ) + xActiveSink->setInputStream( Reference < XInputStream >() ); + + Reference < XActiveDataStreamer > xStreamer( xSink, UNO_QUERY ); + if ( xStreamer.is() ) + xStreamer->setStream( Reference < XStream >() ); + } + + Reference < XActiveDataControl > xControl( xSink, UNO_QUERY ); + if ( xControl.is() ) + xControl->terminate(); + + if ( xProps.is() ) + xProps->removePropertiesChangeListener( + Sequence< OUString >(), + xListener ); + + return ( bAborted || bException ); +} + +/** + Function for opening UCB contents synchronously + */ +static bool UCBOpenContentSync_( + const UcbLockBytesRef& xLockBytes, + const Reference < XContent >& xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + const Reference < XInteractionHandler >& xInteract ) +{ + ::ucbhelper::Content aContent( + xContent, new UcbTaskEnvironment( xInteract, nullptr ), + comphelper::getProcessComponentContext() ); + Reference < XContentIdentifier > xIdent = xContent->getIdentifier(); + OUString aScheme = xIdent->getContentProviderScheme(); + + // http protocol must be handled in a special way: during the opening process the input stream may change + // only the last inputstream after notifying the document headers is valid + if ( !aScheme.equalsIgnoreAsciiCase("http") ) + xLockBytes->SetStreamValid(); + + Reference< XPropertiesChangeListener > xListener = new UcbPropertiesChangeListener_Impl( xLockBytes ); + Reference< XPropertiesChangeNotifier > xProps ( xContent, UNO_QUERY ); + if ( xProps.is() ) + xProps->addPropertiesChangeListener( Sequence< OUString >(), xListener ); + + bool bException = false; + bool bAborted = false; + + try + { + aContent.executeCommand( rArg.Name, rArg.Argument ); + } + catch (const CommandAbortedException&) + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + } + catch (const CommandFailedException&) + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + } + catch (const InteractiveIOException& r) + { + bException = true; + if ( r.Code == IOErrorCode_ACCESS_DENIED || r.Code == IOErrorCode_LOCKING_VIOLATION ) + xLockBytes->SetError( ERRCODE_IO_ACCESSDENIED ); + else if ( r.Code == IOErrorCode_NOT_EXISTING ) + xLockBytes->SetError( ERRCODE_IO_NOTEXISTS ); + else if ( r.Code == IOErrorCode_CANT_READ ) + xLockBytes->SetError( ERRCODE_IO_CANTREAD ); + else + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + catch (const UnsupportedDataSinkException&) + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_NOTSUPPORTED ); + } + catch (const Exception&) + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + if ( bAborted || bException ) + { + Reference < XActiveDataSink > xActiveSink( xSink, UNO_QUERY ); + if ( xActiveSink.is() ) + xActiveSink->setInputStream( Reference < XInputStream >() ); + + Reference < XActiveDataStreamer > xStreamer( xSink, UNO_QUERY ); + if ( xStreamer.is() ) + xStreamer->setStream( Reference < XStream >() ); + } + + Reference < XActiveDataControl > xControl( xSink, UNO_QUERY ); + if ( xControl.is() ) + xControl->terminate(); + + if ( xProps.is() ) + xProps->removePropertiesChangeListener( Sequence< OUString >(), xListener ); + + return ( bAborted || bException ); +} + +UcbLockBytes::UcbLockBytes() + : m_nError( ERRCODE_NONE ) + , m_bTerminated (false) + , m_bDontClose( false ) + , m_bStreamValid (false) +{ + SetSynchronMode(); +} + +UcbLockBytes::~UcbLockBytes() +{ + if ( !m_bDontClose ) + { + if ( m_xInputStream.is() ) + { + try + { + m_xInputStream->closeInput(); + } + catch (const RuntimeException&) + { + } + catch (const IOException&) + { + } + } + } + + if ( m_xInputStream.is() || !m_xOutputStream.is() ) + return; + + try + { + m_xOutputStream->closeOutput(); + } + catch (const RuntimeException&) + { + } + catch (const IOException&) + { + } +} + +Reference < XInputStream > UcbLockBytes::getInputStream() +{ + osl::MutexGuard aGuard( m_aMutex ); + m_bDontClose = true; + return m_xInputStream; +} + +void UcbLockBytes::setStream( const Reference<XStream>& aStream ) +{ + osl::MutexGuard aGuard( m_aMutex ); + if ( aStream.is() ) + { + m_xOutputStream = aStream->getOutputStream(); + setInputStream( aStream->getInputStream(), false ); + m_xSeekable.set( aStream, UNO_QUERY ); + } + else + { + m_xOutputStream.clear(); + setInputStream( Reference < XInputStream >() ); + } +} + +bool UcbLockBytes::setInputStream( const Reference<XInputStream> &rxInputStream, bool bSetXSeekable ) +{ + bool bRet = false; + + try + { + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_bDontClose && m_xInputStream.is() ) + m_xInputStream->closeInput(); + + m_xInputStream = rxInputStream; + + if( bSetXSeekable ) + { + m_xSeekable.set( rxInputStream, UNO_QUERY ); + if( !m_xSeekable.is() && rxInputStream.is() ) + { + Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XOutputStream > rxTempOut( css::io::TempFile::create(xContext), UNO_QUERY_THROW ); + + ::comphelper::OStorageHelper::CopyInputToOutput( rxInputStream, rxTempOut ); + m_xInputStream.set( rxTempOut, UNO_QUERY ); + m_xSeekable.set( rxTempOut, UNO_QUERY ); + } + } + + bRet = m_xInputStream.is(); + } + catch (const Exception&) + { + } + + if ( m_bStreamValid && m_xInputStream.is() ) + m_aInitialized.set(); + + return bRet; +} + +void UcbLockBytes::SetStreamValid() +{ + m_bStreamValid = true; + if ( m_xInputStream.is() ) + m_aInitialized.set(); +} + +void UcbLockBytes::terminate() +{ + m_bTerminated = true; + m_aInitialized.set(); + m_aTerminated.set(); + + if ( GetError() == ERRCODE_NONE && !m_xInputStream.is() ) + { + OSL_FAIL("No InputStream, but no error set!" ); + SetError( ERRCODE_IO_NOTEXISTS ); + } +} + +ErrCode UcbLockBytes::ReadAt(sal_uInt64 const nPos, + void *pBuffer, std::size_t nCount, std::size_t *pRead) const +{ + if ( IsSynchronMode() ) + { + UcbLockBytes* pThis = const_cast < UcbLockBytes* >( this ); + pThis->m_aInitialized.wait(); + } + + Reference <XInputStream> xStream = getInputStream(); + if ( !xStream.is() ) + { + if ( m_bTerminated ) + return ERRCODE_IO_CANTREAD; + else + return ERRCODE_IO_PENDING; + } + + if ( pRead ) + *pRead = 0; + + Reference <XSeekable> xSeekable = getSeekable(); + if ( !xSeekable.is() ) + return ERRCODE_IO_CANTREAD; + + try + { + xSeekable->seek( nPos ); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTSEEK; + } + catch (const css::lang::IllegalArgumentException&) + { + return ERRCODE_IO_CANTSEEK; + } + + sal_Int32 nSize; + + if(nCount > 0x7FFFFFFF) + { + nCount = 0x7FFFFFFF; + } + try + { + if ( !m_bTerminated && !IsSynchronMode() ) + { + sal_uInt64 nLen = xSeekable->getLength(); + if ( nPos + nCount > nLen ) + return ERRCODE_IO_PENDING; + } + + Reference< css::lang::XUnoTunnel > xTunnel( xStream, UNO_QUERY ); + comphelper::ByteReader* pByteReader = nullptr; + if (xTunnel) + pByteReader = reinterpret_cast< comphelper::ByteReader* >( xTunnel->getSomething( comphelper::ByteReader::getUnoTunnelId() ) ); + + if (pByteReader) + { + nSize = pByteReader->readSomeBytes( static_cast<sal_Int8*>(pBuffer), sal_Int32(nCount) ); + } + else + { + Sequence<sal_Int8> aData; + nSize = xStream->readBytes( aData, sal_Int32(nCount) ); + memcpy (pBuffer, aData.getConstArray(), nSize); + } + } + catch (const IOException&) + { + return ERRCODE_IO_CANTREAD; + } + + if (pRead) + *pRead = static_cast<std::size_t>(nSize); + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::WriteAt(sal_uInt64 const nPos, const void *pBuffer, + std::size_t nCount, std::size_t *pWritten) +{ + if ( pWritten ) + *pWritten = 0; + + DBG_ASSERT( IsSynchronMode(), "Writing is only possible in SynchronMode!" ); + DBG_ASSERT( m_aInitialized.check(), "Writing bevor stream is ready!" ); + + Reference <XSeekable> xSeekable = getSeekable(); + Reference <XOutputStream> xOutputStream = getOutputStream(); + if ( !xOutputStream.is() || !xSeekable.is() ) + return ERRCODE_IO_CANTWRITE; + + try + { + xSeekable->seek( nPos ); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTSEEK; + } + + sal_Int8 const * pData = static_cast<sal_Int8 const *>(pBuffer); + Sequence<sal_Int8> aData( pData, nCount ); + try + { + xOutputStream->writeBytes( aData ); + if ( pWritten ) + *pWritten = nCount; + } + catch (const Exception&) + { + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::Flush() const +{ + Reference <XOutputStream > xOutputStream = getOutputStream(); + if ( !xOutputStream.is() ) + return ERRCODE_IO_CANTWRITE; + + try + { + xOutputStream->flush(); + } + catch (const Exception&) + { + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::SetSize (sal_uInt64 const nNewSize) +{ + SvLockBytesStat aStat; + Stat( &aStat ); + std::size_t nSize = aStat.nSize; + + if ( nSize > nNewSize ) + { + Reference < XTruncate > xTrunc( getOutputStream(), UNO_QUERY ); + if ( xTrunc.is() ) + { + xTrunc->truncate(); + nSize = 0; + } + else { + SAL_INFO("unotools.ucbhelper", "Not truncable!"); + } + } + + if ( nSize < nNewSize ) + { + std::size_t nDiff = nNewSize-nSize, nCount=0; + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[ nDiff ]); + memset(pBuffer.get(), 0, nDiff); // initialize for enhanced security + WriteAt( nSize, pBuffer.get(), nDiff, &nCount ); + if ( nCount != nDiff ) + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::Stat( SvLockBytesStat *pStat ) const +{ + if ( IsSynchronMode() ) + { + UcbLockBytes* pThis = const_cast < UcbLockBytes* >( this ); + pThis->m_aInitialized.wait(); + } + + if (!pStat) + return ERRCODE_IO_INVALIDPARAMETER; + + Reference <XInputStream> xStream = getInputStream(); + Reference <XSeekable> xSeekable = getSeekable(); + + if ( !xStream.is() ) + { + if ( m_bTerminated ) + return ERRCODE_IO_INVALIDACCESS; + else + return ERRCODE_IO_PENDING; + } + else if( !xSeekable.is() ) + return ERRCODE_IO_CANTTELL; + + try + { + pStat->nSize = sal_uLong(xSeekable->getLength()); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTTELL; + } + + return ERRCODE_NONE; +} + +UcbLockBytesRef UcbLockBytes::CreateInputLockBytes( const Reference< XInputStream >& xInputStream ) +{ + if( !xInputStream.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->setDontClose(); + xLockBytes->setInputStream( xInputStream ); + xLockBytes->terminate(); + return xLockBytes; +} + +UcbLockBytesRef UcbLockBytes::CreateLockBytes( const Reference< XStream >& xStream ) +{ + if( !xStream.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->setDontClose(); + xLockBytes->setStream( xStream ); + xLockBytes->terminate(); + return xLockBytes; +} + +UcbLockBytesRef UcbLockBytes::CreateLockBytes( const Reference < XContent >& xContent, const Sequence < PropertyValue >& rProps, + StreamMode eOpenMode, const Reference < XInteractionHandler >& xInteractionHandler ) +{ + if( !xContent.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->SetSynchronMode(); + Reference< XActiveDataControl > xSink; + if ( eOpenMode & StreamMode::WRITE ) + xSink = new UcbStreamer_Impl(xLockBytes.get()); + else + xSink = new UcbDataSink_Impl(xLockBytes.get()); + + if ( rProps.hasElements() ) + { + Reference < XCommandProcessor > xProcessor( xContent, UNO_QUERY ); + Command aCommand; + aCommand.Name = "setPropertyValues"; + aCommand.Handle = -1; /* unknown */ + aCommand.Argument <<= rProps; + xProcessor->execute( aCommand, 0, Reference < XCommandEnvironment >() ); + } + + OpenCommandArgument2 aArgument; + aArgument.Sink = xSink; + aArgument.Mode = OpenMode::DOCUMENT; + + Command aCommand; + aCommand.Name = "open"; + aCommand.Argument <<= aArgument; + + bool bError = UCBOpenContentSync( xLockBytes, + xContent, + aCommand, + xSink, + xInteractionHandler ); + + if ( xLockBytes->GetError() == ERRCODE_NONE && ( bError || !xLockBytes->getInputStream().is() ) ) + { + OSL_FAIL("No InputStream, but no error set!" ); + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + return xLockBytes; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucblockbytes.hxx b/unotools/source/ucbhelper/ucblockbytes.hxx new file mode 100644 index 000000000..ca4eac2ab --- /dev/null +++ b/unotools/source/ucbhelper/ucblockbytes.hxx @@ -0,0 +1,139 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/uno/Reference.hxx> + +#include <osl/conditn.hxx> +#include <osl/mutex.hxx> +#include <tools/stream.hxx> +#include <vcl/errcode.hxx> + +namespace com +{ + namespace sun + { + namespace star + { + namespace task + { + class XInteractionHandler; + } + namespace io + { + class XStream; + class XInputStream; + class XOutputStream; + class XSeekable; + } + namespace ucb + { + class XContent; + } + namespace beans + { + struct PropertyValue; + } + } + } +} + +namespace utl +{ +class UcbLockBytes; +typedef tools::SvRef<UcbLockBytes> UcbLockBytesRef; + +class UcbLockBytes : public SvLockBytes +{ + osl::Condition m_aInitialized; + osl::Condition m_aTerminated; + osl::Mutex m_aMutex; + + css::uno::Reference < css::io::XInputStream > m_xInputStream; + css::uno::Reference < css::io::XOutputStream > m_xOutputStream; + css::uno::Reference < css::io::XSeekable > m_xSeekable; + + ErrCode m_nError; + + bool m_bTerminated; + bool m_bDontClose; + bool m_bStreamValid; + + UcbLockBytes(); +protected: + virtual ~UcbLockBytes() override; + +public: + // properties: Referer, PostMimeType + static UcbLockBytesRef CreateLockBytes( const css::uno::Reference < css::ucb::XContent >& xContent, + const css::uno::Sequence < css::beans::PropertyValue >& rProps, + StreamMode eMode, + const css::uno::Reference < css::task::XInteractionHandler >& xInter ); + + static UcbLockBytesRef CreateInputLockBytes( const css::uno::Reference < css::io::XInputStream >& xContent ); + static UcbLockBytesRef CreateLockBytes( const css::uno::Reference < css::io::XStream >& xContent ); + + // SvLockBytes + virtual ErrCode ReadAt(sal_uInt64 nPos, void *pBuffer, std::size_t nCount, std::size_t *pRead) const override; + virtual ErrCode WriteAt(sal_uInt64, const void*, std::size_t, std::size_t *pWritten) override; + virtual ErrCode Flush() const override; + virtual ErrCode SetSize(sal_uInt64) override; + virtual ErrCode Stat ( SvLockBytesStat *pStat ) const override; + + void SetError( ErrCode nError ) + { m_nError = nError; } + + ErrCode const & GetError() const + { return m_nError; } + + // calling this method delegates the responsibility to call closeinput to the caller! + css::uno::Reference < css::io::XInputStream > getInputStream(); + + bool setInputStream( const css::uno::Reference < css::io::XInputStream > &rxInputStream, + bool bSetXSeekable = true ); + void setStream( const css::uno::Reference < css::io::XStream > &rxStream ); + void terminate(); + + css::uno::Reference < css::io::XInputStream > getInputStream() const + { + osl::MutexGuard aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xInputStream; + } + + css::uno::Reference < css::io::XOutputStream > getOutputStream() const + { + osl::MutexGuard aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xOutputStream; + } + + css::uno::Reference < css::io::XSeekable > getSeekable() const + { + osl::MutexGuard aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xSeekable; + } + + void setDontClose() + { m_bDontClose = true; } + + void SetStreamValid(); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucbstreamhelper.cxx b/unotools/source/ucbhelper/ucbstreamhelper.cxx new file mode 100644 index 000000000..e3437a864 --- /dev/null +++ b/unotools/source/ucbhelper/ucbstreamhelper.cxx @@ -0,0 +1,237 @@ +/* -*- 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 <unotools/ucbstreamhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/simplefileaccessinteraction.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/streamwrap.hxx> +#include "ucblockbytes.hxx" + +namespace com::sun::star::ucb { class XCommandEnvironment; } + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +namespace utl +{ + +static std::unique_ptr<SvStream> lcl_CreateStream( const OUString& rFileName, StreamMode eOpenMode, + const Reference < XInteractionHandler >& xInteractionHandler, + bool bEnsureFileExists ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes; + if ( eOpenMode & StreamMode::WRITE ) + { + bool bTruncate = bool( eOpenMode & StreamMode::TRUNC ); + if ( bTruncate ) + { + try + { + // truncate is implemented with deleting the original file + ::ucbhelper::Content aCnt( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + aCnt.executeCommand( "delete", css::uno::Any( true ) ); + } + + catch ( const CommandAbortedException& ) + { + // couldn't truncate/delete + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + } + + if ( bEnsureFileExists || bTruncate ) + { + try + { + // make sure that the desired file exists before trying to open + SvMemoryStream aStream(0,0); + rtl::Reference<::utl::OInputStreamWrapper> xInput = new ::utl::OInputStreamWrapper( aStream ); + + ::ucbhelper::Content aContent( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + InsertCommandArgument aInsertArg; + aInsertArg.Data = xInput; + + aInsertArg.ReplaceExisting = false; + Any aCmdArg; + aCmdArg <<= aInsertArg; + aContent.executeCommand( "insert", aCmdArg ); + } + + // it is NOT an error when the stream already exists and no truncation was desired + catch ( const CommandAbortedException& ) + { + // currently never an error is detected ! + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + } + } + + try + { + // create LockBytes using UCB + ::ucbhelper::Content aContent( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + xLockBytes = UcbLockBytes::CreateLockBytes( aContent.get(), Sequence < PropertyValue >(), + eOpenMode, xInteractionHandler ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + catch ( const CommandAbortedException& ) + { + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream(const OUString& rFileName, StreamMode eOpenMode, css::uno::Reference<css::awt::XWindow> xParentWin) +{ + // related tdf#99312 + // create a specialized interaction handler to manages Web certificates and Web credentials when needed + Reference< XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), xParentWin)); + Reference<XInteractionHandler> xIHScoped(new comphelper::SimpleFileAccessInteraction(xIH)); + + return lcl_CreateStream( rFileName, eOpenMode, xIHScoped, true /* bEnsureFileExists */ ); +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream(const OUString& rFileName, StreamMode eOpenMode, + bool bFileExists, css::uno::Reference<css::awt::XWindow> xParentWin) +{ + // related tdf#99312 + // create a specialized interaction handler to manages Web certificates and Web credentials when needed + Reference< XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), xParentWin)); + Reference<XInteractionHandler> xIHScoped(new comphelper::SimpleFileAccessInteraction(xIH)); + return lcl_CreateStream( rFileName, eOpenMode, xIHScoped,!bFileExists ); +} + + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XInputStream >& xStream ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateInputLockBytes( xStream ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XStream >& xStream ) +{ + std::unique_ptr<SvStream> pStream; + if ( xStream->getOutputStream().is() ) + { + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateLockBytes( xStream ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + else + return CreateStream( xStream->getInputStream() ); + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XInputStream >& xStream, bool bCloseStream ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateInputLockBytes( xStream ); + if ( xLockBytes.is() ) + { + if ( !bCloseStream ) + xLockBytes->setDontClose(); + + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + + return pStream; +}; + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XStream >& xStream, bool bCloseStream ) +{ + std::unique_ptr<SvStream> pStream; + if ( xStream->getOutputStream().is() ) + { + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateLockBytes( xStream ); + if ( xLockBytes.is() ) + { + if ( !bCloseStream ) + xLockBytes->setDontClose(); + + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + else + return CreateStream( xStream->getInputStream(), bCloseStream ); + + return pStream; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/xtempfile.cxx b/unotools/source/ucbhelper/xtempfile.cxx new file mode 100644 index 000000000..a5772d8ba --- /dev/null +++ b/unotools/source/ucbhelper/xtempfile.cxx @@ -0,0 +1,435 @@ +/* -*- 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 "XTempFile.hxx" +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <o3tl/safeint.hxx> +#include <unotools/tempfile.hxx> +#include <cppuhelper/propshlp.hxx> +#include <cppuhelper/supportsservice.hxx> + +OTempFileService::OTempFileService(css::uno::Reference< css::uno::XComponentContext > const &) +: mpStream( nullptr ) +, mbRemoveFile( true ) +, mbInClosed( false ) +, mbOutClosed( false ) +{ + mpTempFile.emplace(); + mpTempFile->EnableKillingFile(); +} + +OTempFileService::~OTempFileService () +{ +} + +// XTypeProvider + +css::uno::Sequence< css::uno::Type > SAL_CALL OTempFileService::getTypes( ) +{ + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<css::beans::XPropertySet>::get() + ,OTempFileBase::getTypes() ); + + return ourTypeCollection.getTypes(); +}; + +// XTempFile + +sal_Bool SAL_CALL OTempFileService::getRemoveFile() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + // the stream is already disconnected + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mbRemoveFile; +}; +void SAL_CALL OTempFileService::setRemoveFile( sal_Bool _removefile ) +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + // the stream is already disconnected + throw css::uno::RuntimeException("Not connected to a file."); + } + + mbRemoveFile = _removefile; + mpTempFile->EnableKillingFile( mbRemoveFile ); +}; +OUString SAL_CALL OTempFileService::getUri() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mpTempFile->GetURL(); + +}; +OUString SAL_CALL OTempFileService::getResourceName() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mpTempFile->GetFileName(); +}; + +// XInputStream + +sal_Int32 SAL_CALL OTempFileService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), static_cast< css::uno::XWeak * >(this)); + + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + sal_uInt32 nRead = mpStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead); + checkError(); + + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} +sal_Int32 SAL_CALL OTempFileService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + { + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + checkError(); + + if (nMaxBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), static_cast < css::uno::XWeak * >( this ) ); + + if (mpStream->eof()) + { + aData.realloc(0); + return 0; + } + } + return readBytes(aData, nMaxBytesToRead); +} +void SAL_CALL OTempFileService::skipBytes( sal_Int32 nBytesToSkip ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + checkError(); + mpStream->SeekRel(nBytesToSkip); + checkError(); +} +sal_Int32 SAL_CALL OTempFileService::available( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + + sal_Int64 nAvailable = mpStream->remainingSize(); + checkError(); + + return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable); +} +void SAL_CALL OTempFileService::closeInput( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + mbInClosed = true; + + if ( mbOutClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +// XOutputStream + +void SAL_CALL OTempFileService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength()); + checkError(); + if ( nWritten != static_cast<sal_uInt32>(aData.getLength())) + throw css::io::BufferSizeExceededException( OUString(),static_cast < css::uno::XWeak * > ( this ) ); +} +void SAL_CALL OTempFileService::flush( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + checkConnected(); + mpStream->Flush(); + checkError(); +} +void SAL_CALL OTempFileService::closeOutput( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); + + mbOutClosed = true; + if (mpStream) + { + // so that if you then open the InputStream, you can read the content + mpStream->FlushBuffer(); + mpStream->Seek(0); + } + + if ( mbInClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +void OTempFileService::checkError () const +{ + if (!mpStream || mpStream->SvStream::GetError () != ERRCODE_NONE ) + throw css::io::NotConnectedException ( OUString(), const_cast < css::uno::XWeak * > ( static_cast < const css::uno::XWeak * > (this ) ) ); +} +void OTempFileService::checkConnected () +{ + if (!mpStream && mpTempFile) + { + // Ideally we should open this SHARE_DENYALL, but the JunitTest_unotools_complex test wants to open + // this file directly and read from it. + mpStream = mpTempFile->GetStream(StreamMode::READ | StreamMode::WRITE + | StreamMode::SHARE_DENYWRITE); + } + + if (!mpStream) + throw css::io::NotConnectedException ( OUString(), static_cast < css::uno::XWeak * > (this ) ); +} + +// XSeekable + +void SAL_CALL OTempFileService::seek( sal_Int64 nLocation ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + checkError(); + sal_Int64 nEndPos = mpStream->TellEnd(); + if ( nLocation < 0 || nLocation > nEndPos ) + throw css::lang::IllegalArgumentException(); + + mpStream->Seek(static_cast<sal_uInt32>(nLocation) ); + checkError(); +} +sal_Int64 SAL_CALL OTempFileService::getPosition( ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + sal_uInt32 nPos = mpStream->Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} +sal_Int64 SAL_CALL OTempFileService::getLength( ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + checkError(); + + sal_Int64 nEndPos = mpStream->TellEnd(); + + return nEndPos; +} + +// XStream + +css::uno::Reference< css::io::XInputStream > SAL_CALL OTempFileService::getInputStream() + { + return css::uno::Reference< css::io::XInputStream >( *this, css::uno::UNO_QUERY ); +} + +css::uno::Reference< css::io::XOutputStream > SAL_CALL OTempFileService::getOutputStream() + { + return css::uno::Reference< css::io::XOutputStream >( *this, css::uno::UNO_QUERY ); + } + +// XTruncate + +void SAL_CALL OTempFileService::truncate() +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + // SetStreamSize() call does not change the position + mpStream->Seek( 0 ); + mpStream->SetStreamSize( 0 ); + checkError(); +} + +#define PROPERTY_HANDLE_URI 1 +#define PROPERTY_HANDLE_REMOVE_FILE 2 +#define PROPERTY_HANDLE_RESOURCE_NAME 3 + +// XPropertySet +::css::uno::Reference< ::css::beans::XPropertySetInfo > OTempFileService::getPropertySetInfo() +{ + // Create a table that map names to index values. + // attention: properties need to be sorted by name! + static cppu::OPropertyArrayHelper ourPropertyInfo( + { + css::beans::Property( "Uri", PROPERTY_HANDLE_URI, cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::READONLY ), + css::beans::Property( "RemoveFile", PROPERTY_HANDLE_REMOVE_FILE, cppu::UnoType<bool>::get(), + 0 ), + css::beans::Property( "ResourceName", PROPERTY_HANDLE_RESOURCE_NAME, cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::READONLY ) + }, + true ); + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( + ::cppu::OPropertySetHelper::createPropertySetInfo( ourPropertyInfo ) ); + return xInfo; +} +void OTempFileService::setPropertyValue( const ::rtl::OUString& aPropertyName, const ::css::uno::Any& aValue ) +{ + if ( aPropertyName == "RemoveFile" ) + setRemoveFile( aValue.get<bool>() ); + else + { + assert(false); + throw css::beans::UnknownPropertyException(aPropertyName); + } +} +::css::uno::Any OTempFileService::getPropertyValue( const ::rtl::OUString& aPropertyName ) +{ + if ( aPropertyName == "RemoveFile" ) + return css::uno::Any(getRemoveFile()); + else if ( aPropertyName == "ResourceName" ) + return css::uno::Any(getResourceName()); + else if ( aPropertyName == "Uri" ) + return css::uno::Any(getUri()); + else + { + assert(false); + throw css::beans::UnknownPropertyException(aPropertyName); + } +} +void OTempFileService::addPropertyChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::removePropertyChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::addVetoableChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::removeVetoableChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& /*xListener*/ ) +{ + assert(false); +} +// XFastPropertySet +void OTempFileService::setFastPropertyValue( ::sal_Int32 nHandle, const ::css::uno::Any& aValue ) +{ + switch (nHandle) + { + case PROPERTY_HANDLE_REMOVE_FILE: setRemoveFile( aValue.get<bool>() ); return; + } + assert(false); + throw css::beans::UnknownPropertyException(OUString::number(nHandle)); +} +::css::uno::Any OTempFileService::getFastPropertyValue( ::sal_Int32 nHandle ) +{ + switch (nHandle) + { + case PROPERTY_HANDLE_REMOVE_FILE: return css::uno::Any(getRemoveFile()); + case PROPERTY_HANDLE_RESOURCE_NAME: return css::uno::Any(getResourceName()); + case PROPERTY_HANDLE_URI: return css::uno::Any(getUri()); + } + assert(false); + throw css::beans::UnknownPropertyException(OUString::number(nHandle)); +} +// XPropertyAccess +::css::uno::Sequence< ::css::beans::PropertyValue > OTempFileService::getPropertyValues() +{ + return { + css::beans::PropertyValue("Uri", PROPERTY_HANDLE_URI, css::uno::Any(getUri()), css::beans::PropertyState_DEFAULT_VALUE), + css::beans::PropertyValue("RemoveFile", PROPERTY_HANDLE_REMOVE_FILE, css::uno::Any(getRemoveFile()), css::beans::PropertyState_DEFAULT_VALUE), + css::beans::PropertyValue("ResourceName", PROPERTY_HANDLE_RESOURCE_NAME, css::uno::Any(getResourceName()), css::beans::PropertyState_DEFAULT_VALUE) + }; +} +void OTempFileService::setPropertyValues( const ::css::uno::Sequence< ::css::beans::PropertyValue >& aProps ) +{ + for ( auto const & rPropVal : aProps ) + setPropertyValue( rPropVal.Name, rPropVal.Value ); +} + +// XServiceInfo +sal_Bool OTempFileService::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} +OUString OTempFileService::getImplementationName() +{ + return "com.sun.star.io.comp.TempFile"; +} +css::uno::Sequence< OUString > OTempFileService::getSupportedServiceNames() +{ + return { "com.sun.star.io.TempFile" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +unotools_OTempFileService_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new OTempFileService(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |