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 /sot/source/sdstor | |
parent | Initial commit. (diff) | |
download | libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.tar.xz libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.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 'sot/source/sdstor')
-rw-r--r-- | sot/source/sdstor/stg.cxx | 939 | ||||
-rw-r--r-- | sot/source/sdstor/stgavl.cxx | 411 | ||||
-rw-r--r-- | sot/source/sdstor/stgavl.hxx | 67 | ||||
-rw-r--r-- | sot/source/sdstor/stgcache.cxx | 430 | ||||
-rw-r--r-- | sot/source/sdstor/stgcache.hxx | 131 | ||||
-rw-r--r-- | sot/source/sdstor/stgdir.cxx | 938 | ||||
-rw-r--r-- | sot/source/sdstor/stgdir.hxx | 111 | ||||
-rw-r--r-- | sot/source/sdstor/stgelem.cxx | 484 | ||||
-rw-r--r-- | sot/source/sdstor/stgelem.hxx | 146 | ||||
-rw-r--r-- | sot/source/sdstor/stgio.cxx | 400 | ||||
-rw-r--r-- | sot/source/sdstor/stgio.hxx | 63 | ||||
-rw-r--r-- | sot/source/sdstor/stgole.cxx | 180 | ||||
-rw-r--r-- | sot/source/sdstor/stgole.hxx | 67 | ||||
-rw-r--r-- | sot/source/sdstor/stgstrms.cxx | 1356 | ||||
-rw-r--r-- | sot/source/sdstor/stgstrms.hxx | 168 | ||||
-rw-r--r-- | sot/source/sdstor/storage.cxx | 803 | ||||
-rw-r--r-- | sot/source/sdstor/storinfo.cxx | 98 | ||||
-rw-r--r-- | sot/source/sdstor/ucbstorage.cxx | 2845 |
18 files changed, 9637 insertions, 0 deletions
diff --git a/sot/source/sdstor/stg.cxx b/sot/source/sdstor/stg.cxx new file mode 100644 index 000000000..08af34309 --- /dev/null +++ b/sot/source/sdstor/stg.cxx @@ -0,0 +1,939 @@ +/* -*- 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 <sot/storinfo.hxx> +#include <osl/file.hxx> +#include <unotools/tempfile.hxx> +#include <tools/stream.hxx> +#include <tools/debug.hxx> + +#include <sot/stg.hxx> +#include <utility> +#include "stgelem.hxx" +#include "stgdir.hxx" +#include "stgio.hxx" +#include "stgole.hxx" + +static tools::Long nTmpCount = 0; + +// The internal open mode is StreamMode::READ | StreamMode::TRUNC, which is silly +// by itself. It inhibits the checking of sharing modes and is used +// during CopyTo() and MoveTo() for opening a stream in read mode +// although it may be open in DENYALL mode + +#define INTERNAL_MODE ( StreamMode::READ | StreamMode::TRUNC ) + +///////////////////////// class StorageBase + + +StorageBase::StorageBase() + : m_bAutoCommit( false ) +{ + m_nMode = StreamMode::READ; + m_nError = ERRCODE_NONE; +} + +StorageBase::~StorageBase() +{ +} + +// The following three methods are declared as const, since they +// may be called from within a const method. + +ErrCode StorageBase::GetError() const +{ + const ErrCode n = m_nError; + m_nError = ERRCODE_NONE; + return n; +} + +void StorageBase::SetError( ErrCode n ) const +{ + if( !m_nError ) + m_nError = n; +} + +void StorageBase::ResetError() const +{ + m_nError = ERRCODE_NONE; +} + +OLEStorageBase::OLEStorageBase( StgIo* p, StgDirEntry* pe, StreamMode& nMode ) + : nStreamMode( nMode ), pIo( p ), pEntry( pe ) +{ + if ( p ) + p->IncRef(); + if( pe ) + pe->m_nRefCnt++; +} + +OLEStorageBase::~OLEStorageBase() +{ + if( pEntry ) + { + DBG_ASSERT( pEntry->m_nRefCnt, "RefCount under 0" ); + if( !--pEntry->m_nRefCnt ) + { + if( pEntry->m_bZombie ) + delete pEntry; + else + pEntry->Close(); + } + + pEntry = nullptr; + } + + + if( pIo && !pIo->DecRef() ) + { + delete pIo; + pIo = nullptr; + } +} + +// Validate the instance for I/O + +bool OLEStorageBase::Validate_Impl( bool bWrite ) const +{ + return pIo + && pIo->m_pTOC + && pEntry + && !pEntry->m_bInvalid + && ( !bWrite || !pEntry->m_bDirect || ( nStreamMode & StreamMode::WRITE ) ); +} + +bool OLEStorageBase::ValidateMode_Impl( StreamMode m, StgDirEntry const * p ) +{ + if( m == INTERNAL_MODE ) + return true; + StreamMode nCurMode = ( p && p->m_nRefCnt ) ? p->m_nMode : StreamMode::SHARE_DENYALL; + if( ( m & StreamMode::READWRITE ) == StreamMode::READ ) + { + // only SHARE_DENYWRITE or SHARE_DENYALL allowed + if( ( ( m & StreamMode::SHARE_DENYWRITE ) + && ( nCurMode & StreamMode::SHARE_DENYWRITE ) ) + || ( ( m & StreamMode::SHARE_DENYALL ) + && ( nCurMode & StreamMode::SHARE_DENYALL ) ) ) + return true; + } + else + { + // only SHARE_DENYALL allowed + // storages open in r/o mode are OK, since only + // the commit may fail + if( ( m & StreamMode::SHARE_DENYALL ) + && ( nCurMode & StreamMode::SHARE_DENYALL ) ) + return true; + } + return false; +} + + +//////////////////////// class StorageStream + + +StorageStream::StorageStream( StgIo* p, StgDirEntry* q, StreamMode m ) + : OLEStorageBase( p, q, m_nMode ), nPos( 0 ) +{ + // The dir entry may be 0; this means that the stream is invalid. + if( q && p ) + { + if( q->m_nRefCnt == 1 ) + { + q->m_nMode = m; + q->OpenStream( *p ); + } + } + else + m &= ~StreamMode::READWRITE; + m_nMode = m; +} + +StorageStream::~StorageStream() +{ + // Do an auto-commit if the entry is open in direct mode + if( m_bAutoCommit ) + Commit(); + if( pEntry && pEntry->m_nRefCnt && pEntry->m_bDirect && (m_nMode & StreamMode::WRITE) ) + pEntry->Commit(); +} + +bool StorageStream::Equals( const BaseStorageStream& rStream ) const +{ + const StorageStream* pOther = dynamic_cast<const StorageStream*>( &rStream ); + return pOther && ( pOther->pEntry == pEntry ); +} + +sal_Int32 StorageStream::Read( void* pData, sal_Int32 nSize ) +{ + if( Validate() ) + { + pEntry->Seek( nPos ); + nSize = pEntry->Read( pData, nSize ); + pIo->MoveError( *this ); + nPos += nSize; + } + else + nSize = 0; + return nSize; +} + +sal_Int32 StorageStream::Write( const void* pData, sal_Int32 nSize ) +{ + if( Validate( true ) ) + { + pEntry->Seek( nPos ); + nSize = pEntry->Write( pData, nSize ); + pIo->MoveError( *this ); + nPos += nSize; + } + else + nSize = 0; + return nSize; +} + +sal_uInt64 StorageStream::Seek( sal_uInt64 n ) +{ + if( Validate() ) + { + nPos = pEntry->Seek( n ); + return nPos; + } + else + return n; +} + +void StorageStream::Flush() +{ + // Flushing means committing, since streams are never transacted + Commit(); +} + +bool StorageStream::SetSize( sal_uInt64 nNewSize ) +{ + if( Validate( true ) ) + { + bool b = pEntry->SetSize( nNewSize ); + pIo->MoveError( *this ); + return b; + } + else + return false; +} + +sal_uInt64 StorageStream::GetSize() const +{ + if( Validate() ) + return pEntry->GetSize(); + return 0; +} + +bool StorageStream::Commit() +{ + if( !Validate() ) + return false; + if( !( m_nMode & StreamMode::WRITE ) ) + { + SetError( SVSTREAM_ACCESS_DENIED ); + return false; + } + else + { + pEntry->Commit(); + pIo->MoveError( *this ); + return Good(); + } +} + +void StorageStream::CopyTo( BaseStorageStream* pDest ) +{ + if( !Validate() || !pDest || !pDest->Validate( true ) || Equals( *pDest ) ) + return; + pEntry->Copy( *pDest ); + pDest->Commit(); + pIo->MoveError( *this ); + SetError( pDest->GetError() ); +} + +bool StorageStream::Validate( bool bValidate ) const +{ + bool bRet = Validate_Impl( bValidate ); + if ( !bRet ) + SetError( SVSTREAM_ACCESS_DENIED ); + return bRet; +} + +bool StorageStream::ValidateMode( StreamMode nMode ) const +{ + bool bRet = ValidateMode_Impl( nMode ); + if ( !bRet ) + SetError( SVSTREAM_ACCESS_DENIED ); + return bRet; +} + +///////////////////////// class SvStorageInfo + +SvStorageInfo::SvStorageInfo( const StgDirEntry& rE ) +{ + rE.m_aEntry.GetName( aName ); + bStorage = rE.m_aEntry.GetType() == STG_STORAGE; + bStream = rE.m_aEntry.GetType() == STG_STREAM; + nSize = bStorage ? 0 : rE.m_aEntry.GetSize(); +} + +/////////////////////////// class Storage + +bool Storage::IsStorageFile( const OUString & rFileName ) +{ + StgIo aIo; + if( aIo.Open( rFileName, StreamMode::STD_READ ) ) + return aIo.Load(); + return false; +} + +bool Storage::IsStorageFile( SvStream* pStream ) +{ + bool bRet = false; + + if ( pStream ) + { + StgHeader aHdr; + sal_uInt64 nPos = pStream->Tell(); + bRet = ( aHdr.Load( *pStream ) && aHdr.Check() ); + + // It's not a stream error if it is too small for an OLE storage header + if ( pStream->GetErrorCode() == ERRCODE_IO_CANTSEEK ) + pStream->ResetError(); + pStream->Seek( nPos ); + } + + return bRet; +} + +// Open the storage file. If writing is permitted and the file is not +// a storage file, initialize it. + + +Storage::Storage( OUString aFile, StreamMode m, bool bDirect ) + : OLEStorageBase( new StgIo, nullptr, m_nMode ) + , aName(std::move( aFile )), bIsRoot( false ) +{ + bool bTemp = false; + if( aName.isEmpty() ) + { + // no name = temporary name! + aName = utl::TempFile::CreateTempName(); + bTemp = true; + } + // the root storage creates the I/O system + m_nMode = m; + if( pIo->Open( aName, m ) ) + { + Init( ( m & ( StreamMode::TRUNC | StreamMode::NOCREATE ) ) == StreamMode::TRUNC ); + if( pEntry ) + { + pEntry->m_bDirect = bDirect; + pEntry->m_nMode = m; + pEntry->m_bTemp = bTemp; + } + } + else + { + pIo->MoveError( *this ); + pEntry = nullptr; + } +} + +// Create a storage on a given stream. + +Storage::Storage( SvStream& r, bool bDirect ) + : OLEStorageBase( new StgIo, nullptr, m_nMode ) + , bIsRoot( false ) +{ + m_nMode = StreamMode::READ; + if( r.IsWritable() ) + m_nMode = StreamMode::READ | StreamMode::WRITE; + if( r.GetError() == ERRCODE_NONE ) + { + pIo->SetStrm( &r, false ); + sal_uInt64 nSize = r.TellEnd(); + r.Seek( 0 ); + // Initializing is OK if the stream is empty + Init( nSize == 0 ); + if( pEntry ) + { + pEntry->m_bDirect = bDirect; + pEntry->m_nMode = m_nMode; + } + pIo->MoveError( *this ); + } + else + { + SetError( r.GetError() ); + pEntry = nullptr; + } +} + + +Storage::Storage( UCBStorageStream& rStrm, bool bDirect ) + : OLEStorageBase( new StgIo, nullptr, m_nMode ), bIsRoot( false ) +{ + m_nMode = StreamMode::READ; + + if ( rStrm.GetError() != ERRCODE_NONE ) + { + SetError( rStrm.GetError() ); + pEntry = nullptr; + return; + } + + SvStream* pStream = rStrm.GetModifySvStream(); + if ( !pStream ) + { + OSL_FAIL( "UCBStorageStream can not provide SvStream implementation!" ); + SetError( SVSTREAM_GENERALERROR ); + pEntry = nullptr; + return; + } + + if( pStream->IsWritable() ) + m_nMode = StreamMode::READ | StreamMode::WRITE; + + pIo->SetStrm( &rStrm ); + + sal_uInt64 nSize = pStream->TellEnd(); + pStream->Seek( 0 ); + // Initializing is OK if the stream is empty + Init( nSize == 0 ); + if( pEntry ) + { + pEntry->m_bDirect = bDirect; + pEntry->m_nMode = m_nMode; + } + + pIo->MoveError( *this ); +} + + +// Perform common code for both ctors above. + +void Storage::Init( bool bCreate ) +{ + pEntry = nullptr; + bool bHdrLoaded = false; + bIsRoot = true; + + OSL_ENSURE( pIo, "The pointer may not be empty at this point!" ); + if( pIo->Good() && pIo->GetStrm() ) + { + sal_uInt64 nSize = pIo->GetStrm()->TellEnd(); + pIo->GetStrm()->Seek( 0 ); + if( nSize ) + { + bHdrLoaded = pIo->Load(); + if( !bHdrLoaded && !bCreate ) + { + // File is not a storage and not empty; do not destroy! + SetError( SVSTREAM_FILEFORMAT_ERROR ); + return; + } + } + } + // file is a storage, empty or should be overwritten + pIo->ResetError(); + // we have to set up the data structures, since + // the file is empty + if( !bHdrLoaded ) + pIo->Init(); + if( pIo->Good() && pIo->m_pTOC ) + { + pEntry = pIo->m_pTOC->GetRoot(); + pEntry->m_nRefCnt++; + } +} + +// Internal ctor + +Storage::Storage( StgIo* p, StgDirEntry* q, StreamMode m ) + : OLEStorageBase( p, q, m_nMode ), bIsRoot( false ) +{ + if( q ) + q->m_aEntry.GetName( aName ); + else + m &= ~StreamMode::READWRITE; + m_nMode = m; + if( q && q->m_nRefCnt == 1 ) + q->m_nMode = m; +} + +Storage::~Storage() +{ + // Invalidate all open substorages + if( m_bAutoCommit ) + Commit(); + if( pEntry ) + { + // Do an auto-commit if the entry is open in direct mode + if( pEntry->m_nRefCnt && pEntry->m_bDirect && (m_nMode & StreamMode::WRITE) ) + Commit(); + if( pEntry->m_nRefCnt == 1 ) + pEntry->Invalidate(false); + } + // close the stream is root storage + if( bIsRoot ) + pIo->Close(); + // remove the file if temporary root storage + if( bIsRoot && pEntry && pEntry->m_bTemp ) + { + osl::File::remove( GetName() ); + } +} + +const OUString& Storage::GetName() const +{ + if( !bIsRoot && Validate() ) + pEntry->m_aEntry.GetName( const_cast<Storage*>(this)->aName ); + return aName; +} + +// Fill in the info list for this storage + +void Storage::FillInfoList( SvStorageInfoList* pList ) const +{ + if( !(Validate() && pList) ) + return; + + StgIterator aIter( *pEntry ); + StgDirEntry* p = aIter.First(); + while( p ) + { + if( !p->m_bInvalid ) + { + SvStorageInfo aInfo( *p ); + pList->push_back( aInfo ); + } + p = aIter.Next(); + } +} + +// Open or create a substorage + +BaseStorage* Storage::OpenUCBStorage( const OUString& rName, StreamMode m, bool bDirect ) +{ + OSL_FAIL("Not supported!"); + return OpenStorage( rName, m, bDirect ); +} + +BaseStorage* Storage::OpenOLEStorage( const OUString& rName, StreamMode m, bool bDirect ) +{ + return OpenStorage( rName, m, bDirect ); +} + +BaseStorage* Storage::OpenStorage( const OUString& rName, StreamMode m, bool bDirect ) +{ + if( !Validate() || !ValidateMode( m ) ) + return new Storage( pIo, nullptr, m ); + if( bDirect && !pEntry->m_bDirect ) + bDirect = false; + + StgDirEntry* p = StgDirStrm::Find( *pEntry, rName ); + if( !p ) + { + if( !( m & StreamMode::NOCREATE ) ) + { + bool bTemp = false; + // create a new storage + OUString aNewName = rName; + if( aNewName.isEmpty() ) + { + aNewName = "Temp Stg " + OUString::number( ++nTmpCount ); + bTemp = true; + } + p = pIo->m_pTOC->Create( *pEntry, aNewName, STG_STORAGE ); + if( p ) + p->m_bTemp = bTemp; + } + if( !p ) + pIo->SetError( ( m & StreamMode::WRITE ) + ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + } + else if( !ValidateMode( m, p ) ) + p = nullptr; + if( p && p->m_aEntry.GetType() != STG_STORAGE ) + { + pIo->SetError( SVSTREAM_FILE_NOT_FOUND ); + p = nullptr; + } + + // Either direct or transacted mode is supported + if( p && pEntry->m_nRefCnt == 1 ) + p->m_bDirect = bDirect; + + // Don't check direct conflict if opening readonly + if( p && (m & StreamMode::WRITE )) + { + if( p->m_bDirect != bDirect ) + SetError( SVSTREAM_ACCESS_DENIED ); + } + Storage* pStg = new Storage( pIo, p, m ); + pIo->MoveError( *pStg ); + if( m & StreamMode::WRITE ) pStg->m_bAutoCommit = true; + return pStg; +} + +// Open a stream + +BaseStorageStream* Storage::OpenStream( const OUString& rName, StreamMode m, bool ) +{ + if( !Validate() || !ValidateMode( m ) ) + return new StorageStream( pIo, nullptr, m ); + StgDirEntry* p = StgDirStrm::Find( *pEntry, rName ); + bool bTemp = false; + if( !p ) + { + if( !( m & StreamMode::NOCREATE ) ) + { + // create a new stream + // make a name if the stream is temporary (has no name) + OUString aNewName( rName ); + if( aNewName.isEmpty() ) + { + aNewName = "Temp Strm " + OUString::number( ++nTmpCount ); + bTemp = true; + } + p = pIo->m_pTOC->Create( *pEntry, aNewName, STG_STREAM ); + } + if( !p ) + pIo->SetError( ( m & StreamMode::WRITE ) + ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + } + else if( !ValidateMode( m, p ) ) + p = nullptr; + // coverity[Resource leak : FALSE] - "Create" method is called with STG_STREAM line 620, + // so we won't enter into this "if" block here. + if( p && p->m_aEntry.GetType() != STG_STREAM ) + { + pIo->SetError( SVSTREAM_FILE_NOT_FOUND ); + p = nullptr; + } + if( p ) + { + p->m_bTemp = bTemp; + p->m_bDirect = pEntry->m_bDirect; + } + StorageStream* pStm = new StorageStream( pIo, p, m ); + if( p && !p->m_bDirect ) + pStm->SetAutoCommit( true ); + pIo->MoveError( *pStm ); + return pStm; +} + +// Delete a stream or substorage by setting the temp bit. + +void Storage::Remove( const OUString& rName ) +{ + if( !Validate( true ) ) + return; + StgDirEntry* p = StgDirStrm::Find( *pEntry, rName ); + if( p ) + { + p->Invalidate( true ); + } + else + { + SetError( SVSTREAM_FILE_NOT_FOUND ); + } +} + +// Copy one element + +bool Storage::CopyTo( const OUString& rElem, BaseStorage* pDest, const OUString& rNew ) +{ + if( !Validate() || !pDest || !pDest->Validate( true ) ) + return false; + StgDirEntry* pElem = StgDirStrm::Find( *pEntry, rElem ); + if( pElem ) + { + if( pElem->m_aEntry.GetType() == STG_STORAGE ) + { + // copy the entire storage + tools::SvRef<BaseStorage> p1 = OpenStorage( rElem, INTERNAL_MODE ); + tools::SvRef<BaseStorage> p2 = pDest->OpenOLEStorage( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pEntry->m_bDirect ); + + if ( p2 ) + { + ErrCode nTmpErr = p2->GetError(); + if( !nTmpErr ) + { + p2->SetClassId( p1->GetClassId() ); + p1->CopyTo( p2.get() ); + SetError( p1->GetError() ); + + nTmpErr = p2->GetError(); + if( !nTmpErr ) + p2->Commit(); + else + pDest->SetError( nTmpErr ); + } + else + pDest->SetError( nTmpErr ); + } + + return Good() && pDest->Good(); + } + else + { + // stream copy + tools::SvRef<BaseStorageStream> p1 = OpenStream( rElem, INTERNAL_MODE ); + tools::SvRef<BaseStorageStream> p2 = pDest->OpenStream( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pEntry->m_bDirect ); + + if ( p2 ) + { + ErrCode nTmpErr = p2->GetError(); + if( !nTmpErr ) + { + p1->CopyTo( p2.get() ); + SetError( p1->GetError() ); + + nTmpErr = p2->GetError(); + if( !nTmpErr ) + p2->Commit(); + else + pDest->SetError( nTmpErr ); + } + else + pDest->SetError( nTmpErr ); + } + + return Good() && pDest->Good(); + } + } + SetError( SVSTREAM_FILE_NOT_FOUND ); + return false; +} + +bool Storage::CopyTo( BaseStorage* pDest ) const +{ + if( !Validate() || !pDest || !pDest->Validate( true ) || Equals( *pDest ) ) + { + SetError( SVSTREAM_ACCESS_DENIED ); + return false; + } + Storage* pThis = const_cast<Storage*>(this); + pDest->SetClassId( GetClassId() ); + pDest->SetDirty(); + SvStorageInfoList aList; + FillInfoList( &aList ); + bool bRes = true; + for( size_t i = 0; i < aList.size() && bRes; i++ ) + { + SvStorageInfo& rInfo = aList[ i ]; + bRes = pThis->CopyTo( rInfo.GetName(), pDest, rInfo.GetName() ); + } + if( !bRes ) + SetError( pDest->GetError() ); + return Good() && pDest->Good(); +} + +bool Storage::IsStorage( const OUString& rName ) const +{ + if( Validate() ) + { + StgDirEntry* p = StgDirStrm::Find( *pEntry, rName ); + if( p ) + return p->m_aEntry.GetType() == STG_STORAGE; + } + return false; +} + +bool Storage::IsStream( const OUString& rName ) const +{ + if( Validate() ) + { + StgDirEntry* p = StgDirStrm::Find( *pEntry, rName ); + if( p ) + return p->m_aEntry.GetType() == STG_STREAM; + } + return false; +} + +bool Storage::IsContained( const OUString& rName ) const +{ + if( Validate() ) + return StgDirStrm::Find( *pEntry, rName ) != nullptr; + else + return false; +} + +// Commit all sub-elements within this storage. If this is +// the root, commit the FAT, the TOC and the header as well. + +bool Storage::Commit() +{ + bool bRes = true; + if( !Validate() ) + return false; + if( !( m_nMode & StreamMode::WRITE ) ) + { + SetError( SVSTREAM_ACCESS_DENIED ); + return false; + } + else + { + // Also commit the sub-streams and Storages + StgIterator aIter( *pEntry ); + for( StgDirEntry* p = aIter.First(); p && bRes; p = aIter.Next() ) + bRes = p->Commit(); + if( bRes && bIsRoot ) + { + bRes = pEntry->Commit(); + if( bRes ) + bRes = pIo->CommitAll(); + } + pIo->MoveError( *this ); + } + return bRes; +} + +bool Storage::Revert() +{ + return true; +} + +///////////////////////////// OLE Support + +// Set the storage type + +void Storage::SetClass( const SvGlobalName & rClass, + SotClipboardFormatId nOriginalClipFormat, + const OUString & rUserTypeName ) +{ + if( Validate( true ) ) + { + // set the class name in the root entry + pEntry->m_aEntry.SetClassId( rClass.GetCLSID() ); + pEntry->SetDirty(); + // then create the streams + StgCompObjStream aCompObj( *this, true ); + aCompObj.GetClsId() = rClass.GetCLSID(); + aCompObj.GetCbFormat() = nOriginalClipFormat; + aCompObj.GetUserName() = rUserTypeName; + if( !aCompObj.Store() ) + SetError( aCompObj.GetError() ); + else + { + StgOleStream aOle(*this); + if( !aOle.Store() ) + SetError( aOle.GetError() ); + } + } + else + SetError( SVSTREAM_ACCESS_DENIED ); +} + +SvGlobalName Storage::GetClassName() +{ + StgCompObjStream aCompObj( *this, false ); + if( aCompObj.Load() ) + return SvGlobalName( aCompObj.GetClsId() ); + pIo->ResetError(); + + if ( pEntry ) + return SvGlobalName( pEntry->m_aEntry.GetClassId() ); + + return SvGlobalName(); +} + +SotClipboardFormatId Storage::GetFormat() +{ + StgCompObjStream aCompObj( *this, false ); + if( aCompObj.Load() ) + return aCompObj.GetCbFormat(); + pIo->ResetError(); + return SotClipboardFormatId::NONE; +} + +OUString Storage::GetUserName() +{ + StgCompObjStream aCompObj( *this, false ); + if( aCompObj.Load() ) + return aCompObj.GetUserName(); + pIo->ResetError(); + return OUString(); +} + +bool Storage::ValidateFAT() +{ + FatError nErr = pIo->ValidateFATs(); + return nErr == FatError::Ok; +} + +void Storage::SetDirty() +{ + if ( pEntry ) + pEntry->SetDirty(); +} + +void Storage::SetClassId( const ClsId& rId ) +{ + if ( pEntry ) + pEntry->m_aEntry.SetClassId( rId ); +} + +const ClsId& Storage::GetClassId() const +{ + if ( pEntry ) + return pEntry->m_aEntry.GetClassId(); + + static const ClsId aDummyId = {0,0,0,{0,0,0,0,0,0,0,0}}; + return aDummyId; +} + +bool Storage::Validate( bool bValidate ) const +{ + bool bRet = Validate_Impl( bValidate ); + if ( !bRet ) + SetError( SVSTREAM_ACCESS_DENIED ); + return bRet; +} + +bool Storage::ValidateMode( StreamMode nMode ) const +{ + bool bRet = ValidateMode_Impl( nMode ); + if ( !bRet ) + SetError( SVSTREAM_ACCESS_DENIED ); + return bRet; +} + +bool Storage::ValidateMode( StreamMode nMode, StgDirEntry const * p ) const +{ + bool bRet = ValidateMode_Impl( nMode, p ); + if ( !bRet ) + SetError( SVSTREAM_ACCESS_DENIED ); + return bRet; +} + +bool Storage::Equals( const BaseStorage& rStorage ) const +{ + const Storage* pOther = dynamic_cast<const Storage*>( &rStorage ); + return pOther && ( pOther->pEntry == pEntry ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgavl.cxx b/sot/source/sdstor/stgavl.cxx new file mode 100644 index 000000000..98a86f3ed --- /dev/null +++ b/sot/source/sdstor/stgavl.cxx @@ -0,0 +1,411 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include "stgavl.hxx" +#include <assert.h> + +StgAvlNode::StgAvlNode() +{ + m_pLeft = m_pRight = nullptr; + m_nBalance = m_nId = 0; +} + +StgAvlNode::~StgAvlNode() +{ + delete m_pLeft; + delete m_pRight; +} + +StgAvlNode* StgAvlNode::Find( StgAvlNode const * pFind ) +{ + if ( pFind ) + { + StgAvlNode* p = this; + while( p ) + { + sal_Int32 nRes = p->Compare( pFind ); + if( !nRes ) + return p; + else p = ( nRes < 0 ) ? p->m_pLeft : p->m_pRight; + } + } + return nullptr; +} + +// find point to add node to AVL tree and returns +// +/0/- for >/=/< previous + +sal_Int32 StgAvlNode::Locate + ( StgAvlNode const * pFind, + StgAvlNode** pPivot, StgAvlNode **pParent, StgAvlNode** pPrev ) +{ + sal_Int32 nRes = 0; + StgAvlNode* pCur = this; + + OSL_ENSURE( pPivot && pParent && pPrev, "The pointers may not be NULL!" ); + *pParent = *pPrev = nullptr; + *pPivot = this; + + // search tree for insertion point + if ( pFind ) + { + while( pCur != nullptr ) + { + // check for pPivot + if( pCur->m_nBalance != 0 ) + { + *pPivot = pCur; + *pParent = *pPrev; + } + // save pPrev location and see what direction to go + *pPrev = pCur; + nRes = pCur->Compare( pFind ); + if( nRes == 0 ) + break; + else pCur = ( nRes < 0 ) ? pCur->m_pLeft : pCur->m_pRight; + } + } + + return nRes; +} + +// adjust balance factors in AVL tree from pivot down. +// Returns delta balance. + +short StgAvlNode::Adjust( StgAvlNode** pHeavy, StgAvlNode const * pNew ) +{ + StgAvlNode* pCur = this; + short nDelta; + // no traversing + OSL_ENSURE( pHeavy && pNew, "The pointers is not allowed to be NULL!" ); + if( pCur == pNew || !pNew ) + return m_nBalance; + + sal_Int32 nRes = Compare( pNew ); + if( nRes > 0 ) + { + *pHeavy = pCur = m_pRight; + nDelta = -1; + } + else + { + *pHeavy = pCur = m_pLeft; + nDelta = 1; + } + m_nBalance = 0; + while( pCur != pNew ) + { + nRes = pCur->Compare( pNew ); + if( nRes > 0 ) + { + // height of right increases by 1 + pCur->m_nBalance = -1; + pCur = pCur->m_pRight; + } + else + { + // height of left increases by 1 + pCur->m_nBalance = 1; + pCur = pCur->m_pLeft; + } + } + m_nBalance = m_nBalance + nDelta; + return nDelta; +} + +// perform LL rotation and return new root + +StgAvlNode* StgAvlNode::RotLL() +{ + assert(m_pLeft && "The pointer is not allowed to be NULL!"); + StgAvlNode *pHeavy = m_pLeft; + m_pLeft = pHeavy->m_pRight; + pHeavy->m_pRight = this; + pHeavy->m_nBalance = m_nBalance = 0; + return pHeavy; +} + +// perform LR rotation and return new root + +StgAvlNode* StgAvlNode::RotLR() +{ + assert(m_pLeft && m_pLeft->m_pRight && "The pointer is not allowed to be NULL!"); + StgAvlNode* pHeavy = m_pLeft; + StgAvlNode* pNewRoot = pHeavy->m_pRight; + + pHeavy->m_pRight = pNewRoot->m_pLeft; + m_pLeft = pNewRoot->m_pRight; + pNewRoot->m_pLeft = pHeavy; + pNewRoot->m_pRight = this; + + switch( pNewRoot->m_nBalance ) + { + case 1: // LR( b ) + m_nBalance = -1; + pHeavy->m_nBalance = 0; + break; + case -1: // LR( c ) + pHeavy->m_nBalance = 1; + m_nBalance = 0; + break; + case 0: // LR( a ) + m_nBalance = 0; + pHeavy->m_nBalance = 0; + break; + } + pNewRoot->m_nBalance = 0; + return pNewRoot; +} + +// perform RR rotation and return new root +StgAvlNode* StgAvlNode::RotRR() +{ + assert(m_pRight && "The pointer is not allowed to be NULL!" ); + StgAvlNode* pHeavy = m_pRight; + m_pRight = pHeavy->m_pLeft; + pHeavy->m_pLeft = this; + m_nBalance = pHeavy->m_nBalance = 0; + return pHeavy; +} + +// perform the RL rotation and return the new root +StgAvlNode* StgAvlNode::RotRL() +{ + assert(m_pRight && m_pRight->m_pLeft && "The pointer is not allowed to be NULL!"); + StgAvlNode* pHeavy = m_pRight; + StgAvlNode* pNewRoot = pHeavy->m_pLeft; + pHeavy->m_pLeft = pNewRoot->m_pRight; + m_pRight = pNewRoot->m_pLeft; + pNewRoot->m_pRight = pHeavy; + pNewRoot->m_pLeft = this; + switch( pNewRoot->m_nBalance ) + { + case -1: // RL( b ) + m_nBalance = 1; + pHeavy->m_nBalance = 0; + break; + case 1: // RL( c ) + pHeavy->m_nBalance = -1; + m_nBalance = 0; + break; + case 0: // RL( a ) + m_nBalance = 0; + pHeavy->m_nBalance = 0; + break; + } + pNewRoot->m_nBalance = 0; + return pNewRoot; +} + +// Remove a tree element. Return the removed element or NULL. + +StgAvlNode* StgAvlNode::Rem( StgAvlNode** p, StgAvlNode* pDel, bool bPtrs ) +{ + if( p && *p && pDel ) + { + StgAvlNode* pCur = *p; + sal_Int32 nRes = bPtrs ? sal_Int32( pCur == pDel ) : pCur->Compare( pDel ); + if( !nRes ) + { + // Element found: remove + if( !pCur->m_pRight ) + { + *p = pCur->m_pLeft; pCur->m_pLeft = nullptr; + } + else if( !pCur->m_pLeft ) + { + *p = pCur->m_pRight; pCur->m_pRight = nullptr; + } + else + { + // The damn element has two leaves. Get the + // rightmost element of the left subtree (which + // is lexically before this element) and replace + // this element with the element found. + StgAvlNode* last = pCur; + StgAvlNode* l; + for( l = pCur->m_pLeft; + l->m_pRight; last = l, l = l->m_pRight ) {} + // remove the element from chain + if( l == last->m_pRight ) + last->m_pRight = l->m_pLeft; + else + last->m_pLeft = l->m_pLeft; + // perform the replacement + l->m_pLeft = pCur->m_pLeft; + l->m_pRight = pCur->m_pRight; + *p = l; + // delete the element + pCur->m_pLeft = pCur->m_pRight = nullptr; + } + return pCur; + } + else + { + if( nRes < 0 ) + return Rem( &pCur->m_pLeft, pDel, bPtrs ); + else + return Rem( &pCur->m_pRight, pDel, bPtrs ); + } + } + return nullptr; +} + +// Enumerate the tree for later iteration + +void StgAvlNode::StgEnum( short& n ) +{ + if( m_pLeft ) + m_pLeft->StgEnum( n ); + m_nId = n++; + if( m_pRight ) + m_pRight->StgEnum( n ); +} + +// Add node to AVL tree. +// Return false if the element already exists. + +bool StgAvlNode::Insert( StgAvlNode** pRoot, StgAvlNode* pIns ) +{ + StgAvlNode* pPivot, *pHeavy, *pParent, *pPrev; + if ( !pRoot ) + return false; + + // special case - empty tree + if( *pRoot == nullptr ) + { + *pRoot = pIns; + return true; + } + // find insertion point and return if already present + sal_Int32 nRes = (*pRoot)->Locate( pIns, &pPivot, &pParent, &pPrev ); + if( !nRes ) + return false; + + assert(pPivot && pPrev && "The pointers may not be NULL!"); + + // add new node + if( nRes < 0 ) + pPrev->m_pLeft = pIns; + else + pPrev->m_pRight = pIns; + // rebalance tree + short nDelta = pPivot->Adjust( &pHeavy, pIns ); + if( pPivot->m_nBalance >= 2 || pPivot->m_nBalance <= -2 ) + { + StgAvlNode* pNewRoot; + pHeavy = ( nDelta < 0 ) ? pPivot->m_pRight : pPivot->m_pLeft; + // left imbalance + if( nDelta > 0 ) + if( pHeavy->m_nBalance == 1 ) + pNewRoot = pPivot->RotLL(); + else + pNewRoot = pPivot->RotLR(); + // right imbalance + else if( pHeavy->m_nBalance == -1 ) + pNewRoot = pPivot->RotRR(); + else + pNewRoot = pPivot->RotRL(); + // relink balanced subtree + if( pParent == nullptr ) + *pRoot = pNewRoot; + else if( pPivot == pParent->m_pLeft ) + pParent->m_pLeft = pNewRoot; + else if( pPivot == pParent->m_pRight ) + pParent->m_pRight = pNewRoot; + } + return true; +} + +// Remove node from tree. Returns true is found and removed. +// Actually delete if bDel + +bool StgAvlNode::Remove( StgAvlNode** pRoot, StgAvlNode* pDel, bool bDel ) +{ + if ( !pRoot ) + return false; + + // special case - empty tree + if( *pRoot == nullptr ) + return false; + // delete the element + pDel = Rem( pRoot, pDel, false ); + if( pDel ) + { + if( bDel ) + delete pDel; + // Rebalance the tree the hard way + // OS 22.09.95: On MD's request commented out due to crash +/* StgAvlNode* pNew = NULL; + while( *pRoot ) + { + StgAvlNode* p = Rem( pRoot, *pRoot, false ); + Insert( &pNew, p ); + } + *pRoot = pNew;*/ + return true; + } + else + return false; +} + +// Move node to a different tree. Returns true is found and moved. This routine +// may be called when the key has changed. + + +////////////////////////// class AvlIterator + +// The iterator walks through a tree one entry by one. + +StgAvlIterator::StgAvlIterator( StgAvlNode* p ) +{ + m_pRoot = p; + m_nCur = 0; + if( p ) + { + short nCount = 0; // tree size + p->StgEnum( nCount ); + } +} + +StgAvlNode* StgAvlIterator::Find( short n ) +{ + StgAvlNode* p = m_pRoot; + while( p ) + { + if( n == p->m_nId ) + break; + else p = ( n < p->m_nId ) ? p->m_pLeft : p->m_pRight; + } + return p; +} + +StgAvlNode* StgAvlIterator::First() +{ + m_nCur = -1; + return Next(); +} + +StgAvlNode* StgAvlIterator::Next() +{ + return Find( ++m_nCur ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgavl.hxx b/sot/source/sdstor/stgavl.hxx new file mode 100644 index 000000000..c78df1722 --- /dev/null +++ b/sot/source/sdstor/stgavl.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGAVL_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGAVL_HXX + +#include <sal/types.h> + +// This is an abstract base class for nodes. +// Especially, the compare function must be implemented. + +class StgAvlNode +{ + friend class StgAvlIterator; +private: + sal_Int32 Locate( StgAvlNode const *, StgAvlNode**, StgAvlNode**, StgAvlNode** ); + short Adjust( StgAvlNode**, StgAvlNode const * ); + StgAvlNode* RotLL(); + StgAvlNode* RotLR(); + StgAvlNode* RotRR(); + StgAvlNode* RotRL(); + void StgEnum( short& ); + static StgAvlNode* Rem( StgAvlNode**, StgAvlNode*, bool ); +protected: + short m_nId; // iterator ID + short m_nBalance; // indicates tree balance + StgAvlNode* m_pLeft, *m_pRight; // leaves + StgAvlNode(); +public: + virtual ~StgAvlNode(); + StgAvlNode* Find( StgAvlNode const * ); + static bool Insert( StgAvlNode**, StgAvlNode* ); + static bool Remove( StgAvlNode**, StgAvlNode*, bool bDel ); + virtual sal_Int32 Compare( const StgAvlNode* ) const = 0; +}; + +// The iterator class provides single stepping through an AVL tree. + +class StgAvlIterator { + StgAvlNode* m_pRoot; // root entry (parent) + short m_nCur; // current element + StgAvlNode* Find( short ); +public: + explicit StgAvlIterator( StgAvlNode* ); + StgAvlNode* First(); + StgAvlNode* Next(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgcache.cxx b/sot/source/sdstor/stgcache.cxx new file mode 100644 index 000000000..93f7d3090 --- /dev/null +++ b/sot/source/sdstor/stgcache.cxx @@ -0,0 +1,430 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <string.h> +#include <o3tl/safeint.hxx> +#include <osl/endian.h> +#include <osl/diagnose.h> + +#include <sot/stg.hxx> +#include "stgcache.hxx" + +#include <algorithm> + +////////////////////////////// class StgPage +// This class implements buffer functionality. The cache will always return +// a page buffer, even if a read fails. It is up to the caller to determine +// the correctness of the I/O. + +StgPage::StgPage( short nSize, sal_Int32 nPage ) + : mnPage( nPage ) + , mpData( new sal_uInt8[ nSize ] ) + , mnSize( nSize ) +{ + OSL_ENSURE( mnSize >= 512, "Unexpected page size is provided!" ); + // We will write this data to a permanent file later + // best to clear if first. + memset( mpData.get(), 0, mnSize ); +} + +StgPage::~StgPage() +{ +} + +rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage ) +{ + return rtl::Reference< StgPage >( new StgPage( nData, nPage ) ); +} + +void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal ) +{ + if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) ) + { +#ifdef OSL_BIGENDIAN + nVal = OSL_SWAPDWORD(nVal); +#endif + static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal; + SetDirty( rPage ); + } +} + +bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB ) +{ + return pA->mnPage < pB->mnPage; +} + +//////////////////////////////// class StgCache + +// The disk cache holds the cached sectors. The sector type differ according +// to their purpose. + +static sal_Int32 lcl_GetPageCount( sal_uInt64 nFileSize, short nPageSize ) +{ +// return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0; + // #i61980# real life: last page may be incomplete, return number of *started* pages + return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0; +} + +StgCache::StgCache() + : m_nError( ERRCODE_NONE ) + , m_nPages( 0 ) + , m_nRef( 0 ) + , m_nReplaceIdx( 0 ) + , maLRUPages( 8 ) // entries in the LRU lookup + , m_nPageSize( 512 ) + , m_pStorageStream( nullptr ) + , m_pStrm( nullptr ) + , m_bMyStream( false ) + , m_bFile( false ) +{ +} + +StgCache::~StgCache() +{ + Clear(); + SetStrm( nullptr, false ); +} + +void StgCache::SetPhysPageSize( short n ) +{ + OSL_ENSURE( n >= 512, "Unexpected page size is provided!" ); + if ( n >= 512 ) + { + m_nPageSize = n; + sal_uInt64 nFileSize = m_pStrm->TellEnd(); + m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize ); + } +} + +// Create a new cache element + +rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg ) +{ + rtl::Reference< StgPage > xElem( StgPage::Create( m_nPageSize, nPg ) ); + maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem; + return xElem; +} + +// Delete the given element + +void StgCache::Erase( const rtl::Reference< StgPage > &xElem ) +{ + OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" ); + if ( xElem.is() ) { + auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(), + [xElem](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == xElem->GetPage(); }); + if (it != maLRUPages.end()) + it->clear(); + } +} + +// remove all cache elements without flushing them + +void StgCache::Clear() +{ + maDirtyPages.clear(); + for (auto& rxPage : maLRUPages) + rxPage.clear(); +} + +// Look for a cached page + +rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage ) +{ + auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(), + [nPage](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == nPage; }); + if (it != maLRUPages.end()) + return *it; + IndexToStgPage::iterator it2 = maDirtyPages.find( nPage ); + if ( it2 != maDirtyPages.end() ) + return it2->second; + return rtl::Reference< StgPage >(); +} + +// Load a page into the cache + +rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce ) +{ + rtl::Reference< StgPage > p = Find( nPage ); + if( !p.is() ) + { + p = Create( nPage ); + if( !Read( nPage, p->GetData() ) && bForce ) + { + Erase( p ); + p.clear(); + SetError( SVSTREAM_READ_ERROR ); + } + } + return p; +} + +// Copy an existing page into a new page. Use this routine +// to duplicate an existing stream or to create new entries. +// The new page is initially marked dirty. No owner is copied. + +rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld ) +{ + rtl::Reference< StgPage > p = Find( nNew ); + if( !p.is() ) + p = Create( nNew ); + if( nOld >= 0 ) + { + // old page: we must have this data! + rtl::Reference< StgPage > q = Get( nOld, true ); + if( q.is() ) + { + OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" ); + memcpy( p->GetData(), q->GetData(), p->GetSize() ); + } + } + SetDirty( p ); + + return p; +} + +// Historically this wrote pages in a sorted, ascending order; +// continue that tradition. +bool StgCache::Commit() +{ + if ( Good() ) // otherwise Write does nothing + { + std::vector< StgPage * > aToWrite; + aToWrite.reserve(maDirtyPages.size()); + for (const auto& rEntry : maDirtyPages) + aToWrite.push_back( rEntry.second.get() ); + + std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater ); + for (StgPage* pWr : aToWrite) + { + const rtl::Reference< StgPage > &pPage = pWr; + if ( !Write( pPage->GetPage(), pPage->GetData() ) ) + return false; + } + } + + maDirtyPages.clear(); + + m_pStrm->Flush(); + SetError( m_pStrm->GetError() ); + + return true; +} + +// Set a stream + +void StgCache::SetStrm( SvStream* p, bool bMy ) +{ + if( m_pStorageStream ) + { + m_pStorageStream->ReleaseRef(); + m_pStorageStream = nullptr; + } + + if( m_bMyStream ) + delete m_pStrm; + m_pStrm = p; + m_bMyStream = bMy; +} + +void StgCache::SetStrm( UCBStorageStream* pStgStream ) +{ + if( m_pStorageStream ) + m_pStorageStream->ReleaseRef(); + m_pStorageStream = pStgStream; + + if( m_bMyStream ) + delete m_pStrm; + + m_pStrm = nullptr; + + if ( m_pStorageStream ) + { + m_pStorageStream->AddFirstRef(); + m_pStrm = m_pStorageStream->GetModifySvStream(); + } + + m_bMyStream = false; +} + +void StgCache::SetDirty( const rtl::Reference< StgPage > &rPage ) +{ + assert( m_pStrm && m_pStrm->IsWritable() ); + maDirtyPages[ rPage->GetPage() ] = rPage; +} + +// Open/close the disk file + +bool StgCache::Open( const OUString& rName, StreamMode nMode ) +{ + // do not open in exclusive mode! + if( nMode & StreamMode::SHARE_DENYALL ) + nMode = ( ( nMode & ~StreamMode::SHARE_DENYALL ) | StreamMode::SHARE_DENYWRITE ); + SvFileStream* pFileStrm = new SvFileStream( rName, nMode ); + // SvStream "feature" Write Open also successful if it does not work + bool bAccessDenied = false; + if( ( nMode & StreamMode::WRITE ) && !pFileStrm->IsWritable() ) + { + pFileStrm->Close(); + bAccessDenied = true; + } + SetStrm( pFileStrm, true ); + if( pFileStrm->IsOpen() ) + { + sal_uInt64 nFileSize = m_pStrm->TellEnd(); + m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize ); + m_pStrm->Seek( 0 ); + } + else + m_nPages = 0; + m_bFile = true; + SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : m_pStrm->GetError() ); + return Good(); +} + +void StgCache::Close() +{ + if( m_bFile ) + { + static_cast<SvFileStream*>(m_pStrm)->Close(); + SetError( m_pStrm->GetError() ); + } +} + +// low level I/O + +bool StgCache::Read( sal_Int32 nPage, void* pBuf ) +{ + sal_uInt32 nRead = 0, nBytes = m_nPageSize; + if( Good() ) + { + /* #i73846# real life: a storage may refer to a page one-behind the + last valid page (see document attached to the issue). In that case + (if nPage==nPages), just do nothing here and let the caller work on + the empty zero-filled buffer. */ + if ( nPage > m_nPages ) + SetError( SVSTREAM_READ_ERROR ); + else if ( nPage < m_nPages ) + { + sal_uInt32 nPos; + sal_Int32 nPg2; + // fixed address and size for the header + if( nPage == -1 ) + { + nPos = 0; + nPg2 = 1; + nBytes = 512; + } + else + { + nPos = Page2Pos(nPage); + nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1; + } + + if (m_pStrm->Tell() != nPos) + m_pStrm->Seek(nPos); + + if (nPg2 != 1) + SetError(SVSTREAM_READ_ERROR); + else + { + nRead = m_pStrm->ReadBytes(pBuf, nBytes); + SetError(m_pStrm->GetError()); + } + } + } + + if (!Good()) + return false; + + if (nRead != nBytes) + memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead); + return true; +} + +bool StgCache::Write( sal_Int32 nPage, void const * pBuf ) +{ + if( Good() ) + { + sal_uInt32 nPos = Page2Pos( nPage ); + sal_uInt32 nBytes = m_nPageSize; + + // fixed address and size for the header + // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import + if( nPage == -1 ) + { + nPos = 0; + nBytes = 512; + } + if( m_pStrm->Tell() != nPos ) + { + m_pStrm->Seek(nPos); + } + size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes ); + if( nRes != nBytes ) + SetError( SVSTREAM_WRITE_ERROR ); + else + SetError( m_pStrm->GetError() ); + } + return Good(); +} + +// set the file size in pages + +bool StgCache::SetSize( sal_Int32 n ) +{ + // Add the file header + sal_Int32 nSize = n * m_nPageSize + 512; + m_pStrm->SetStreamSize( nSize ); + SetError( m_pStrm->GetError() ); + if( !m_nError ) + m_nPages = n; + return Good(); +} + +void StgCache::SetError( ErrCode n ) +{ + if( n && !m_nError ) + m_nError = n; +} + +void StgCache::ResetError() +{ + m_nError = ERRCODE_NONE; + m_pStrm->ResetError(); +} + +void StgCache::MoveError( StorageBase const & r ) +{ + if( m_nError != ERRCODE_NONE ) + { + r.SetError( m_nError ); + ResetError(); + } +} + +// Utility functions + +sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const +{ + if( nPage < 0 ) nPage = 0; + return( nPage * m_nPageSize ) + m_nPageSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgcache.hxx b/sot/source/sdstor/stgcache.hxx new file mode 100644 index 000000000..7efbffae5 --- /dev/null +++ b/sot/source/sdstor/stgcache.hxx @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGCACHE_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGCACHE_HXX + +#include <o3tl/safeint.hxx> +#include <osl/endian.h> +#include <rtl/ref.hxx> +#include <tools/stream.hxx> +#include "stgelem.hxx" +#include <salhelper/simplereferenceobject.hxx> + +#include <memory> +#include <unordered_map> + +class UCBStorageStream; +class StgPage; +class StorageBase; + +class StgCache +{ + typedef std::unordered_map + < + sal_Int32, rtl::Reference< StgPage > + > IndexToStgPage; + + typedef std::vector< rtl::Reference< StgPage > > LRUList; + + ErrCode m_nError; // error code + sal_Int32 m_nPages; // size of data area in pages + sal_uInt16 m_nRef; // reference count + IndexToStgPage maDirtyPages; // hash of all dirty pages + int m_nReplaceIdx; // index into maLRUPages to replace next + LRUList maLRUPages; // list of last few non-dirty pages. + short m_nPageSize; // page size of the file + UCBStorageStream* m_pStorageStream; // holds reference to UCB storage stream + + void Erase( const rtl::Reference< StgPage >& ); // delete a cache element + rtl::Reference< StgPage > Create( sal_Int32 ); // create a cached page + SvStream* m_pStrm; // physical stream + bool m_bMyStream; // true: delete stream in dtor +protected: + bool m_bFile; // true: file stream + sal_Int32 Page2Pos( sal_Int32 ) const; // page address --> file position +public: + StgCache(); + ~StgCache(); + void IncRef() { m_nRef++; } + sal_uInt16 DecRef() { return --m_nRef; } + void SetPhysPageSize( short ); + sal_Int32 GetPhysPages() const { return m_nPages; } + short GetPhysPageSize() const { return m_nPageSize; } + SvStream* GetStrm() { return m_pStrm; } + void SetStrm( SvStream*, bool ); + void SetStrm( UCBStorageStream* ); + bool Good() const { return m_nError == ERRCODE_NONE; } + ErrCode const & GetError() const { return m_nError; } + void MoveError( StorageBase const & ); + void SetError( ErrCode ); + void ResetError(); + bool Open( const OUString& rName, StreamMode ); + void Close(); + bool Read( sal_Int32 nPage, void* pBuf ); + bool Write( sal_Int32 nPage, void const * pBuf ); + + // two routines for accessing FAT pages + // Assume that the data is a FAT page and get/put FAT data. + void SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal ); + static inline sal_Int32 GetFromPage ( const rtl::Reference< StgPage >& rPage, short nOff ); + void SetDirty ( const rtl::Reference< StgPage > &rPage ); + bool SetSize( sal_Int32 nPages ); + rtl::Reference< StgPage > Find( sal_Int32 ); // find a cached page + rtl::Reference< StgPage > Get( sal_Int32, bool ); // get a cached page + rtl::Reference< StgPage > Copy( sal_Int32, sal_Int32=STG_FREE ); // copy a page + bool Commit(); // flush all pages + void Clear(); // clear the cache +}; + +class StgPage : public salhelper::SimpleReferenceObject +{ + const sal_Int32 mnPage; // page index + std::unique_ptr<sal_uInt8[]> + mpData; // nSize bytes + short mnSize; // size of this page + StgPage( short nData, sal_Int32 nPage ); + virtual ~StgPage() override; +public: + StgPage(const StgPage&) = delete; + StgPage& operator=(const StgPage&) = delete; + static rtl::Reference< StgPage > Create( short nData, sal_Int32 nPage ); + + sal_Int32 GetPage() const { return mnPage; } + void* GetData() { return mpData.get(); } + short GetSize() const { return mnSize; } + +public: + static bool IsPageGreater( const StgPage *pA, const StgPage *pB ); +}; + +inline sal_Int32 StgCache::GetFromPage ( const rtl::Reference< StgPage >& rPage, short nOff ) +{ + if( nOff < 0 || ( o3tl::make_unsigned(nOff) >= rPage->GetSize() / sizeof( sal_Int32 ) ) ) + return -1; + sal_Int32 n = static_cast<sal_Int32*>(rPage->GetData())[ nOff ]; +#ifdef OSL_BIGENDIAN + return OSL_SWAPDWORD(n); +#else + return n; +#endif +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgdir.cxx b/sot/source/sdstor/stgdir.cxx new file mode 100644 index 000000000..69ca094f3 --- /dev/null +++ b/sot/source/sdstor/stgdir.cxx @@ -0,0 +1,938 @@ +/* -*- 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 <sot/stg.hxx> +#include "stgelem.hxx" +#include "stgstrms.hxx" +#include "stgdir.hxx" +#include "stgio.hxx" + +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <memory> + +//////////////////////////// class StgDirEntry + +// This class holds the dir entry data and maintains dirty flags for both +// the entry and the data. + +// Transacted mode for streams: On the first write, a temp stream pTmpStrm +// is created and operated on. A commit moves pTmpStrm to pCurStrm, which +// is used for subsequent reads. A new write creates a new copy of pTmpStrm +// based on pCurStrm. Reverting throws away pTmpStrm. +// Transacted mode for storages: A copy of the dir ents is kept in aSave. +// Committing means copying aEntry to aSave. Reverting means to copy aSave +// to aEntry, delete newly created entries and to reactivate removed entries. + +// Problem of implementation: No hierarchical commits. Therefore only +// overall transaction-oriented or direct. + +StgDirEntry::StgDirEntry( const void* pBuffer, sal_uInt32 nBufferLen, sal_uInt64 nUnderlyingStreamSize, bool * pbOk ) +{ + *pbOk = m_aEntry.Load( pBuffer, nBufferLen, nUnderlyingStreamSize ); + + InitMembers(); +} + +StgDirEntry::StgDirEntry( const StgEntry& r ) : m_aEntry( r ) +{ + InitMembers(); +} + +// Helper for all ctors + +void StgDirEntry::InitMembers() +{ + m_aSave = m_aEntry; + m_pUp = + m_pDown = nullptr; + m_pStgStrm = nullptr; + m_pCurStrm = + m_pTmpStrm = nullptr; + m_nPos = + m_nEntry = + m_nRefCnt = 0; + m_nMode = StreamMode::READ; + m_bDirect = true; + m_bInvalid = + m_bRemoved = + m_bTemp = + m_bDirty = + m_bZombie = false; +} + +StgDirEntry::~StgDirEntry() +{ + Close(); + delete m_pCurStrm; + delete m_pStgStrm; + delete m_pDown; +} + +// Comparison function + +sal_Int32 StgDirEntry::Compare( const StgAvlNode* p ) const +{ + sal_Int32 nResult = -1; + if ( p ) + { + const StgDirEntry* pEntry = static_cast<const StgDirEntry*>(p); + nResult = m_aEntry.Compare( pEntry->m_aEntry ); + } + return nResult; +} + +// Enumerate the entry numbers. +// n is incremented to show the total # of entries. +// These number are later used as page numbers when storing +// the TOC tree into the TOC stream. Remember that aSave is +// stored, not aEntry. + +void StgDirEntry::Enum( sal_Int32& n ) +{ + sal_Int32 nLeft = STG_FREE, nRight = STG_FREE, nDown = STG_FREE; + m_nEntry = n++; + if( m_pLeft ) + { + static_cast<StgDirEntry*>(m_pLeft)->Enum( n ); + nLeft = static_cast<StgDirEntry*>(m_pLeft)->m_nEntry; + } + if( m_pRight ) + { + static_cast<StgDirEntry*>(m_pRight)->Enum( n ); + nRight = static_cast<StgDirEntry*>(m_pRight)->m_nEntry; + } + if( m_pDown ) + { + m_pDown->Enum( n ); nDown = m_pDown->m_nEntry; + } + m_aSave.SetLeaf( STG_LEFT, nLeft ); + m_aSave.SetLeaf( STG_RIGHT, nRight ); + m_aSave.SetLeaf( STG_CHILD, nDown ); +} + +// Delete all temporary entries before writing the TOC stream. +// Until now Deltemp is never called with bForce True + +void StgDirEntry::DelTemp( bool bForce ) +{ + if( m_pLeft ) + static_cast<StgDirEntry*>(m_pLeft)->DelTemp( false ); + if( m_pRight ) + static_cast<StgDirEntry*>(m_pRight)->DelTemp( false ); + if( m_pDown ) + { + // If the storage is dead, of course all elements are dead, too + if( m_bInvalid && m_aEntry.GetType() == STG_STORAGE ) + bForce = true; + m_pDown->DelTemp( bForce ); + } + if( !( bForce || m_bInvalid ) || ( m_aEntry.GetType() == STG_ROOT ) ) + return; + + Close(); + if( m_pUp ) + { + // this deletes the element if refcnt == 0! + bool bDel = m_nRefCnt == 0; + StgAvlNode::Remove( reinterpret_cast<StgAvlNode**>(&m_pUp->m_pDown), this, bDel ); + if( !bDel ) + { + m_pLeft = m_pRight = m_pDown = nullptr; + m_bInvalid = m_bZombie = true; + } + } +} + +// Save the tree into the given dir stream + +bool StgDirEntry::Store( StgDirStrm& rStrm ) +{ + void* pEntry = rStrm.GetEntry( m_nEntry, true ); + if( !pEntry ) + return false; + // Do not store the current (maybe not committed) entry + m_aSave.Store( pEntry ); + if( m_pLeft ) + if( !static_cast<StgDirEntry*>(m_pLeft)->Store( rStrm ) ) + return false; + if( m_pRight ) + if( !static_cast<StgDirEntry*>(m_pRight)->Store( rStrm ) ) + return false; + if( m_pDown && !m_pDown->Store( rStrm ) ) + return false; + return true; +} + +bool StgDirEntry::StoreStream( StgIo& rIo ) +{ + if( m_aEntry.GetType() == STG_STREAM || m_aEntry.GetType() == STG_ROOT ) + { + if( m_bInvalid ) + { + // Delete the stream if needed + if( !m_pStgStrm ) + { + OpenStream( rIo ); + delete m_pStgStrm; + m_pStgStrm = nullptr; + } + else + m_pStgStrm->SetSize( 0 ); + } + // or write the data stream + else if( !Tmp2Strm() ) + return false; + } + return true; +} + +// Save all dirty streams + +bool StgDirEntry::StoreStreams( StgIo& rIo ) +{ + if( !StoreStream( rIo ) ) + return false; + if( m_pLeft ) + if( !static_cast<StgDirEntry*>(m_pLeft)->StoreStreams( rIo ) ) + return false; + if( m_pRight ) + if( !static_cast<StgDirEntry*>(m_pRight)->StoreStreams( rIo ) ) + return false; + if( m_pDown ) + if( !m_pDown->StoreStreams( rIo ) ) + return false; + return true; +} + +// Revert all directory entries after failure to write the TOC stream + +void StgDirEntry::RevertAll() +{ + m_aEntry = m_aSave; + if( m_pLeft ) + static_cast<StgDirEntry*>(m_pLeft)->RevertAll(); + if( m_pRight ) + static_cast<StgDirEntry*>(m_pRight)->RevertAll(); + if( m_pDown ) + m_pDown->RevertAll(); +} + +// Look if any element of the tree is dirty + +bool StgDirEntry::IsDirty() +{ + if( m_bDirty || m_bInvalid ) + return true; + if( m_pLeft && static_cast<StgDirEntry*>(m_pLeft)->IsDirty() ) + return true; + if( m_pRight && static_cast<StgDirEntry*>(m_pRight)->IsDirty() ) + return true; + if( m_pDown && m_pDown->IsDirty() ) + return true; + return false; +} + +// Set up a stream. + +void StgDirEntry::OpenStream( StgIo& rIo ) +{ + sal_Int32 nThreshold = static_cast<sal_uInt16>(rIo.m_aHdr.GetThreshold()); + delete m_pStgStrm; + if( m_aEntry.GetSize() < nThreshold ) + m_pStgStrm = new StgSmallStrm( rIo, *this ); + else + m_pStgStrm = new StgDataStrm( rIo, *this ); + if( m_bInvalid && m_aEntry.GetSize() ) + { + // This entry has invalid data, so delete that data + SetSize( 0 ); +// bRemoved = bInvalid = false; + } + m_nPos = 0; +} + +// Close the open stream without committing. If the entry is marked as +// temporary, delete it. +// Do not delete pCurStrm here! +// (TLX:??? At least pStgStrm must be deleted.) + +void StgDirEntry::Close() +{ + delete m_pTmpStrm; + m_pTmpStrm = nullptr; +// nRefCnt = 0; + m_bInvalid = m_bTemp; +} + +// Get the current stream size + +sal_Int32 StgDirEntry::GetSize() const +{ + sal_Int32 n; + if( m_pTmpStrm ) + n = m_pTmpStrm->GetSize(); + else if( m_pCurStrm ) + n = m_pCurStrm->GetSize(); + else n = m_aEntry.GetSize(); + return n; +} + +// Set the stream size. This means also creating a temp stream. + +bool StgDirEntry::SetSize( sal_Int32 nNewSize ) +{ + if ( + !( m_nMode & StreamMode::WRITE ) || + (!m_bDirect && !m_pTmpStrm && !Strm2Tmp()) + ) + { + return false; + } + + if( nNewSize < m_nPos ) + m_nPos = nNewSize; + if( m_pTmpStrm ) + { + m_pTmpStrm->SetSize( nNewSize ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + return m_pTmpStrm->GetError() == ERRCODE_NONE; + } + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + + bool bRes = false; + StgIo& rIo = m_pStgStrm->GetIo(); + sal_Int32 nThreshold = rIo.m_aHdr.GetThreshold(); + // ensure the correct storage stream! + StgStrm* pOld = nullptr; + sal_uInt16 nOldSize = 0; + if( nNewSize >= nThreshold && m_pStgStrm->IsSmallStrm() ) + { + pOld = m_pStgStrm; + nOldSize = static_cast<sal_uInt16>(pOld->GetSize()); + m_pStgStrm = new StgDataStrm( rIo, STG_EOF, 0 ); + } + else if( nNewSize < nThreshold && !m_pStgStrm->IsSmallStrm() ) + { + pOld = m_pStgStrm; + nOldSize = static_cast<sal_uInt16>(nNewSize); + m_pStgStrm = new StgSmallStrm( rIo, STG_EOF ); + } + // now set the new size + if( m_pStgStrm->SetSize( nNewSize ) ) + { + // did we create a new stream? + if( pOld ) + { + // if so, we probably need to copy the old data + if( nOldSize ) + { + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nOldSize ]); + pOld->Pos2Page( 0 ); + m_pStgStrm->Pos2Page( 0 ); + if( pOld->Read( pBuf.get(), nOldSize ) + && m_pStgStrm->Write( pBuf.get(), nOldSize ) ) + bRes = true; + } + else + bRes = true; + if( bRes ) + { + pOld->SetSize( 0 ); + delete pOld; + m_pStgStrm->Pos2Page( m_nPos ); + m_pStgStrm->SetEntry( *this ); + } + else + { + m_pStgStrm->SetSize( 0 ); + delete m_pStgStrm; + m_pStgStrm = pOld; + } + } + else + { + m_pStgStrm->Pos2Page( m_nPos ); + bRes = true; + } + } + return bRes; + } +} + +// Seek. On negative values, seek to EOF. + +sal_Int32 StgDirEntry::Seek( sal_Int32 nNew ) +{ + if( m_pTmpStrm ) + { + if( nNew < 0 ) + nNew = m_pTmpStrm->GetSize(); + nNew = m_pTmpStrm->Seek( nNew ); + } + else if( m_pCurStrm ) + { + if( nNew < 0 ) + nNew = m_pCurStrm->GetSize(); + nNew = m_pCurStrm->Seek( nNew ); + } + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return m_nPos; + + sal_Int32 nSize = m_aEntry.GetSize(); + + if( nNew < 0 ) + nNew = nSize; + + // try to enlarge, readonly streams do not allow this + if( nNew > nSize ) + { + if ( !( m_nMode & StreamMode::WRITE ) || !SetSize( nNew ) ) + { + return m_nPos; + } + else + return Seek( nNew ); + } + m_pStgStrm->Pos2Page( nNew ); + nNew = m_pStgStrm->GetPos(); + } + + m_nPos = nNew; + return m_nPos; +} + +// Read + +sal_Int32 StgDirEntry::Read( void* p, sal_Int32 nLen ) +{ + if( nLen <= 0 ) + return 0; + if( m_pTmpStrm ) + nLen = m_pTmpStrm->ReadBytes( p, nLen ); + else if( m_pCurStrm ) + nLen = m_pCurStrm->ReadBytes( p, nLen ); + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return 0; + + nLen = m_pStgStrm->Read( p, nLen ); + } + + m_nPos += nLen; + return nLen; +} + +// Write + +sal_Int32 StgDirEntry::Write( const void* p, sal_Int32 nLen ) +{ + if( nLen <= 0 || !( m_nMode & StreamMode::WRITE ) ) + return 0; + + // Was this stream committed internally and reopened in direct mode? + if( m_bDirect && ( m_pCurStrm || m_pTmpStrm ) && !Tmp2Strm() ) + return 0; + // Is this stream opened in transacted mode? Do we have to make a copy? + if( !m_bDirect && !m_pTmpStrm && !Strm2Tmp() ) + return 0; + + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return 0; + + if( m_pTmpStrm ) + { + nLen = m_pTmpStrm->WriteBytes( p, nLen ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + } + else + { + sal_Int32 nNew = m_nPos + nLen; + if( nNew > m_pStgStrm->GetSize() ) + { + if( !SetSize( nNew ) ) + return 0; + m_pStgStrm->Pos2Page( m_nPos ); + } + nLen = m_pStgStrm->Write( p, nLen ); + } + m_nPos += nLen; + return nLen; +} + +void StgDirEntry::Copy( BaseStorageStream& rDest ) +{ + sal_Int32 n = GetSize(); + if( !(rDest.SetSize( n ) && n) ) + return; + + sal_uInt64 Pos = rDest.Tell(); + sal_uInt8 aTempBytes[ 4096 ]; + void* p = static_cast<void*>( aTempBytes ); + Seek( 0 ); + rDest.Seek( 0 ); + while( n ) + { + sal_Int32 nn = n; + if( nn > 4096 ) + nn = 4096; + if( Read( p, nn ) != nn ) + break; + if( sal::static_int_cast<sal_Int32>(rDest.Write( p, nn )) != nn ) + break; + n -= nn; + } + rDest.Seek( Pos ); // ?! Seems to be undocumented ! +} + +// Commit this entry + +bool StgDirEntry::Commit() +{ + // OSL_ENSURE( nMode & StreamMode::WRITE, "Trying to commit readonly stream!" ); + + m_aSave = m_aEntry; + bool bRes = true; + if( m_aEntry.GetType() == STG_STREAM ) + { + if( m_pTmpStrm ) + { + delete m_pCurStrm; + m_pCurStrm = m_pTmpStrm; + m_pTmpStrm = nullptr; + } + if( m_bRemoved ) + // Delete the stream if needed + if( m_pStgStrm ) + m_pStgStrm->SetSize( 0 ); + } + else if( m_aEntry.GetType() == STG_STORAGE && m_bDirect && bRes ) + { + StgIterator aIter( *this ); + for( StgDirEntry* p = aIter.First(); p && bRes; p = aIter.Next() ) + bRes = p->Commit(); + } + return bRes; +} + +// Copy the stg stream to the temp stream + +bool StgDirEntry::Strm2Tmp() +{ + if( !m_pTmpStrm ) + { + sal_uInt64 n = 0; + if( m_pCurStrm ) + { + // It was already committed once + m_pTmpStrm = new StgTmpStrm; + if( m_pTmpStrm->GetError() == ERRCODE_NONE && m_pTmpStrm->Copy( *m_pCurStrm ) ) + return true; + n = 1; // indicates error + } + else + { + n = m_aEntry.GetSize(); + m_pTmpStrm = new StgTmpStrm( n ); + if( m_pTmpStrm->GetError() == ERRCODE_NONE ) + { + if( n ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + + sal_uInt8 aTempBytes[ 4096 ]; + void* p = static_cast<void*>( aTempBytes ); + m_pStgStrm->Pos2Page( 0 ); + while( n ) + { + sal_uInt64 nn = n; + if( nn > 4096 ) + nn = 4096; + if( static_cast<sal_uInt64>(m_pStgStrm->Read( p, nn )) != nn ) + break; + if (m_pTmpStrm->WriteBytes( p, nn ) != nn) + break; + n -= nn; + } + m_pStgStrm->Pos2Page( m_nPos ); + m_pTmpStrm->Seek( m_nPos ); + } + } + else + n = 1; + } + + if( n ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( m_pStgStrm ) + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + + delete m_pTmpStrm; + m_pTmpStrm = nullptr; + return false; + } + } + return true; +} + +// Copy the temp stream to the stg stream during the final commit + +bool StgDirEntry::Tmp2Strm() +{ + // We did commit once, but have not written since then + if( !m_pTmpStrm ) + { + m_pTmpStrm = m_pCurStrm; + m_pCurStrm = nullptr; + } + if( m_pTmpStrm ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + sal_uInt64 n = m_pTmpStrm->GetSize(); + std::unique_ptr<StgStrm> pNewStrm; + StgIo& rIo = m_pStgStrm->GetIo(); + sal_uInt64 nThreshold = rIo.m_aHdr.GetThreshold(); + if( n < nThreshold ) + pNewStrm.reset(new StgSmallStrm( rIo, STG_EOF )); + else + pNewStrm.reset(new StgDataStrm( rIo, STG_EOF )); + if( pNewStrm->SetSize( n ) ) + { + sal_uInt8 p[ 4096 ]; + m_pTmpStrm->Seek( 0 ); + while( n ) + { + sal_uInt64 nn = n; + if( nn > 4096 ) + nn = 4096; + if (m_pTmpStrm->ReadBytes( p, nn ) != nn) + break; + if( static_cast<sal_uInt64>(pNewStrm->Write( p, nn )) != nn ) + break; + n -= nn; + } + if( n ) + { + m_pTmpStrm->Seek( m_nPos ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + return false; + } + else + { + m_pStgStrm->SetSize( 0 ); + delete m_pStgStrm; + m_pStgStrm = pNewStrm.release(); + m_pStgStrm->SetEntry(*this); + m_pStgStrm->Pos2Page(m_nPos); + delete m_pTmpStrm; + delete m_pCurStrm; + m_pTmpStrm = m_pCurStrm = nullptr; + m_aSave = m_aEntry; + } + } + } + return true; +} + +// Invalidate all open entries by setting the RefCount to 0. If the bDel +// flag is set, also set the invalid flag to indicate deletion during the +// next dir stream flush. + +void StgDirEntry::Invalidate( bool bDel ) +{ +// nRefCnt = 0; + if( bDel ) + m_bRemoved = m_bInvalid = true; + switch( m_aEntry.GetType() ) + { + case STG_STORAGE: + case STG_ROOT: + { + StgIterator aIter( *this ); + for( StgDirEntry* p = aIter.First(); p; p = aIter.Next() ) + p->Invalidate( bDel ); + break; + } + default: + break; + } +} + +///////////////////////////// class StgDirStrm + +// This specialized stream is the maintenance stream for the directory tree. + +StgDirStrm::StgDirStrm( StgIo& r ) + : StgDataStrm( r, r.m_aHdr.GetTOCStart(), -1 ) + , m_pRoot( nullptr ) +{ + if( r.GetError() ) + return; + if( m_nStart == STG_EOF ) + { + StgEntry aRoot; + aRoot.Init(); + static constexpr OUStringLiteral sRootEntry = u"Root Entry"; + aRoot.SetName( sRootEntry ); + aRoot.SetType( STG_ROOT ); + m_pRoot = new StgDirEntry( std::move(aRoot) ); + m_pRoot->SetDirty(); + } + else + { + // temporarily use this instance as owner, so + // the TOC pages can be removed. + m_pEntry = reinterpret_cast<StgDirEntry*>(this); // just for a bit pattern + SetupEntry( 0, m_pRoot ); + m_pEntry = nullptr; + } +} + +StgDirStrm::~StgDirStrm() +{ + delete m_pRoot; +} + +// Recursively parse the directory tree during reading the TOC stream + +void StgDirStrm::SetupEntry( sal_Int32 n, StgDirEntry* pUpper ) +{ + void* p = ( n == STG_FREE ) ? nullptr : GetEntry( n, false ); + if( !p ) + return; + + SvStream *pUnderlyingStream = m_rIo.GetStrm(); + sal_uInt64 nUnderlyingStreamSize = pUnderlyingStream->TellEnd(); + + bool bOk(false); + std::unique_ptr<StgDirEntry> pCur(new StgDirEntry( p, STGENTRY_SIZE, nUnderlyingStreamSize, &bOk )); + + if( !bOk ) + { + m_rIo.SetError( SVSTREAM_GENERALERROR ); + // an error occurred + return; + } + + // better it is + if( !pUpper ) + pCur->m_aEntry.SetType( STG_ROOT ); + + sal_Int32 nLeft = pCur->m_aEntry.GetLeaf( STG_LEFT ); + sal_Int32 nRight = pCur->m_aEntry.GetLeaf( STG_RIGHT ); + // substorage? + sal_Int32 nLeaf = STG_FREE; + if( pCur->m_aEntry.GetType() == STG_STORAGE || pCur->m_aEntry.GetType() == STG_ROOT ) + { + nLeaf = pCur->m_aEntry.GetLeaf( STG_CHILD ); + if (nLeaf != STG_FREE && nLeaf == n) + { + m_rIo.SetError( SVSTREAM_GENERALERROR ); + return; + } + } + + if( !(nLeaf != 0 && nLeft != 0 && nRight != 0) ) + return; + + //fdo#41642 + StgDirEntry *pUp = pUpper; + while (pUp) + { + if (pUp->m_aEntry.GetLeaf(STG_CHILD) == nLeaf) + { + SAL_WARN("sot", "Leaf node of upper StgDirEntry is same as current StgDirEntry's leaf node. Circular entry chain, discarding link"); + return; + } + pUp = pUp->m_pUp; + } + + if( StgAvlNode::Insert + ( reinterpret_cast<StgAvlNode**>( pUpper ? &pUpper->m_pDown : &m_pRoot ), pCur.get() ) ) + { + pCur->m_pUp = pUpper; + } + else + { + // bnc#682484: There are some really broken docs out there + // that contain duplicate entries in 'Directory' section + // so don't set the error flag here and just skip those + // (was: rIo.SetError( SVSTREAM_CANNOT_MAKE );) + return; + } + SetupEntry( nLeft, pUpper ); + SetupEntry( nRight, pUpper ); + SetupEntry( nLeaf, pCur.release() ); +} + +// Extend or shrink the directory stream. + +bool StgDirStrm::SetSize( sal_Int32 nBytes ) +{ + // Always allocate full pages + if ( nBytes < 0 ) + nBytes = 0; + + nBytes = ( ( nBytes + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize; + return StgStrm::SetSize( nBytes ); +} + +// Save the TOC stream into a new substream after saving all data streams + +bool StgDirStrm::Store() +{ + if( !m_pRoot || !m_pRoot->IsDirty() ) + return true; + if( !m_pRoot->StoreStreams( m_rIo ) ) + return false; + // After writing all streams, the data FAT stream has changed, + // so we have to commit the root again + m_pRoot->Commit(); + // We want a completely new stream, so fake an empty stream + sal_Int32 nOldStart = m_nStart; // save for later deletion + sal_Int32 nOldSize = m_nSize; + m_nStart = m_nPage = STG_EOF; + m_nSize = 0; + SetPos(0, true); + m_nOffset = 0; + // Delete all temporary entries + m_pRoot->DelTemp( false ); + // set the entry numbers + sal_Int32 n = 0; + m_pRoot->Enum( n ); + if( !SetSize( n * STGENTRY_SIZE ) ) + { + m_nStart = nOldStart; m_nSize = nOldSize; + m_pRoot->RevertAll(); + return false; + } + // set up the cache elements for the new stream + if( !Copy( STG_FREE, m_nSize ) ) + { + m_pRoot->RevertAll(); + return false; + } + // Write the data to the new stream + if( !m_pRoot->Store( *this ) ) + { + m_pRoot->RevertAll(); + return false; + } + // fill any remaining entries with empty data + sal_Int32 ne = m_nSize / STGENTRY_SIZE; + StgEntry aEmpty; + aEmpty.Init(); + while( n < ne ) + { + void* p = GetEntry( n++, true ); + if( !p ) + { + m_pRoot->RevertAll(); + return false; + } + aEmpty.Store( p ); + } + // Now we can release the old stream + m_pFat->FreePages( nOldStart, true ); + m_rIo.m_aHdr.SetTOCStart( m_nStart ); + return true; +} + +// Get a dir entry. + +void* StgDirStrm::GetEntry( sal_Int32 n, bool bDirty ) +{ + return n < 0 || n >= m_nSize / STGENTRY_SIZE + ? nullptr : GetPtr( n * STGENTRY_SIZE, bDirty ); +} + +// Find a dir entry. + +StgDirEntry* StgDirStrm::Find( StgDirEntry& rStg, const OUString& rName ) +{ + if( rStg.m_pDown ) + { + StgEntry aEntry; + aEntry.Init(); + aEntry.SetName( rName ); + // Look in the directory attached to the entry + StgDirEntry aTest( std::move(aEntry) ); + return static_cast<StgDirEntry*>( rStg.m_pDown->Find( &aTest ) ); + } + else + return nullptr; +} + +// Create a new entry. + +StgDirEntry* StgDirStrm::Create( StgDirEntry& rStg, const OUString& rName, StgEntryType eType ) +{ + StgEntry aEntry; + aEntry.Init(); + aEntry.SetType( eType ); + aEntry.SetName( rName ); + StgDirEntry* pRes = Find( rStg, rName ); + if( pRes ) + { + if( !pRes->m_bInvalid ) + { + m_rIo.SetError( SVSTREAM_CANNOT_MAKE ); + return nullptr; + } + pRes->m_bInvalid = + pRes->m_bRemoved = + pRes->m_bTemp = false; + pRes->m_bDirty = true; + return pRes; + } + else + { + std::unique_ptr<StgDirEntry> pNewRes(new StgDirEntry( std::move(aEntry) )); + if( StgAvlNode::Insert( reinterpret_cast<StgAvlNode**>(&rStg.m_pDown), pNewRes.get() ) ) + { + pNewRes->m_pUp = &rStg; + pNewRes->m_bDirty = true; + } + else + { + m_rIo.SetError( SVSTREAM_CANNOT_MAKE ); + pNewRes.reset(); + } + return pNewRes.release(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgdir.hxx b/sot/source/sdstor/stgdir.hxx new file mode 100644 index 000000000..3605c27ac --- /dev/null +++ b/sot/source/sdstor/stgdir.hxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGDIR_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGDIR_HXX + +#include "stgavl.hxx" +#include "stgelem.hxx" +#include "stgstrms.hxx" + +class StgIo; +class StgDirStrm; + +class BaseStorageStream; +class StgDirEntry : public StgAvlNode +{ + friend class StgIterator; + friend class StgDirStrm; + StgEntry m_aSave; // original dir entry + StgDirEntry* m_pUp; // parent directory + StgDirEntry* m_pDown; // child directory for storages + StgStrm* m_pStgStrm; // storage stream + StgTmpStrm* m_pTmpStrm; // temporary stream + StgTmpStrm* m_pCurStrm; // temp stream after commit + sal_Int32 m_nEntry; // entry # in TOC stream (temp) + sal_Int32 m_nPos; // current position + bool m_bDirty; // dirty directory entry + bool m_bRemoved; // removed per Invalidate() + void InitMembers(); // ctor helper + virtual sal_Int32 Compare( const StgAvlNode* ) const override; + bool StoreStream( StgIo& ); // store the stream + bool StoreStreams( StgIo& ); // store all streams + void RevertAll(); // revert the whole tree + bool Strm2Tmp(); // copy stgstream to temp file + bool Tmp2Strm(); // copy temp file to stgstream +public: + StgEntry m_aEntry; // entry data + sal_Int32 m_nRefCnt; // reference count + StreamMode m_nMode; // open mode + bool m_bTemp; // true: delete on dir flush + bool m_bDirect; // true: direct mode + bool m_bZombie; // true: Removed From StgIo + bool m_bInvalid; // true: invalid entry + StgDirEntry(const void* pBuffer, sal_uInt32 nBufferLen, + sal_uInt64 nUnderlyingStreamSize, bool * pbOk); + explicit StgDirEntry( const StgEntry& ); + virtual ~StgDirEntry() override; + + void Invalidate( bool ); // invalidate all open entries + void Enum( sal_Int32& ); // enumerate entries for iteration + void DelTemp( bool ); // delete temporary entries + bool Store( StgDirStrm& ); // save entry into dir strm + + void SetDirty() { m_bDirty = true; } + bool IsDirty(); + + bool Commit(); + + void OpenStream( StgIo& ); // set up an appropriate stream + void Close(); + sal_Int32 GetSize() const; + bool SetSize( sal_Int32 ); + sal_Int32 Seek( sal_Int32 ); + sal_Int32 Read( void*, sal_Int32 ); + sal_Int32 Write( const void*, sal_Int32 ); + void Copy( BaseStorageStream& ); +}; + +class StgDirStrm : public StgDataStrm +{ + friend class StgIterator; + StgDirEntry* m_pRoot; // root of dir tree + void SetupEntry( sal_Int32, StgDirEntry* ); +public: + explicit StgDirStrm( StgIo& ); + virtual ~StgDirStrm() override; + virtual bool SetSize( sal_Int32 ) override; // change the size + bool Store(); + void* GetEntry( sal_Int32 n, bool );// get an entry + StgDirEntry* GetRoot() { return m_pRoot; } + static StgDirEntry* Find( StgDirEntry&, const OUString& ); + StgDirEntry* Create( StgDirEntry&, const OUString&, StgEntryType ); +}; + +class StgIterator : public StgAvlIterator +{ +public: + explicit StgIterator( StgDirEntry& rStg ) : StgAvlIterator( rStg.m_pDown ) {} + StgDirEntry* First() { return static_cast<StgDirEntry*>( StgAvlIterator::First() ); } + StgDirEntry* Next() { return static_cast<StgDirEntry*>( StgAvlIterator::Next() ); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgelem.cxx b/sot/source/sdstor/stgelem.cxx new file mode 100644 index 000000000..ff41d8d70 --- /dev/null +++ b/sot/source/sdstor/stgelem.cxx @@ -0,0 +1,484 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <string.h> + +#include <o3tl/safeint.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <unotools/charclass.hxx> +#include <sot/stg.hxx> +#include "stgelem.hxx" +#include "stgio.hxx" + +const sal_uInt16 nMaxLegalStr = 31; + +const sal_uInt8 cStgSignature[ 8 ] = { 0xD0,0xCF,0x11,0xE0,0xA1,0xB1,0x1A,0xE1 }; + +////////////////////////////// struct ClsId + +SvStream& ReadClsId( SvStream& r, ClsId& rId ) +{ + r.ReadUInt32( rId.Data1 ) + .ReadUInt16( rId.Data2 ) + .ReadUInt16( rId.Data3 ) + .ReadUChar( rId.Data4[0] ) + .ReadUChar( rId.Data4[1] ) + .ReadUChar( rId.Data4[2] ) + .ReadUChar( rId.Data4[3] ) + .ReadUChar( rId.Data4[4] ) + .ReadUChar( rId.Data4[5] ) + .ReadUChar( rId.Data4[6] ) + .ReadUChar( rId.Data4[7] ); + return r; +} + +SvStream& WriteClsId( SvStream& r, const ClsId& rId ) +{ + return + r .WriteUInt32( rId.Data1 ) + .WriteUInt16( rId.Data2 ) + .WriteUInt16( rId.Data3 ) + .WriteUChar( rId.Data4[0] ) + .WriteUChar( rId.Data4[1] ) + .WriteUChar( rId.Data4[2] ) + .WriteUChar( rId.Data4[3] ) + .WriteUChar( rId.Data4[4] ) + .WriteUChar( rId.Data4[5] ) + .WriteUChar( rId.Data4[6] ) + .WriteUChar( rId.Data4[7] ); +} + +///////////////////////////// class StgHeader + +StgHeader::StgHeader() +: m_nVersion( 0 ) +, m_nByteOrder( 0 ) +, m_nPageSize( 0 ) +, m_nDataPageSize( 0 ) +, m_bDirty( sal_uInt8(false) ) +, m_nFATSize( 0 ) +, m_nTOCstrm( 0 ) +, m_nReserved( 0 ) +, m_nThreshold( 0 ) +, m_nDataFAT( 0 ) +, m_nDataFATSize( 0 ) +, m_nMasterChain( 0 ) +, m_nMaster( 0 ) +{ +} + +void StgHeader::Init() +{ + memcpy( m_cSignature, cStgSignature, 8 ); + memset( &m_aClsId, 0, sizeof( ClsId ) ); + m_nVersion = 0x0003003B; + m_nByteOrder = 0xFFFE; + m_nPageSize = 9; // 512 bytes + m_nDataPageSize = 6; // 64 bytes + m_bDirty = sal_uInt8(false); + memset( m_cReserved, 0, sizeof( m_cReserved ) ); + m_nFATSize = 0; + m_nTOCstrm = 0; + m_nReserved = 0; + m_nThreshold = 4096; + m_nDataFAT = 0; + m_nDataFATSize = 0; + m_nMasterChain = STG_EOF; + + SetTOCStart( STG_EOF ); + SetDataFATStart( STG_EOF ); + for( short i = 0; i < cFATPagesInHeader; i++ ) + SetFATPage( i, STG_FREE ); +} + +bool StgHeader::Load( StgIo& rIo ) +{ + bool bResult = false; + if ( rIo.GetStrm() ) + { + SvStream& r = *rIo.GetStrm(); + bResult = Load( r ); + bResult = ( bResult && rIo.Good() ); + } + + return bResult; +} + +bool StgHeader::Load( SvStream& r ) +{ + r.Seek( 0 ); + r.ReadBytes( m_cSignature, 8 ); + ReadClsId( r, m_aClsId ); // 08 Class ID + r.ReadInt32( m_nVersion ) // 1A version number + .ReadUInt16( m_nByteOrder ) // 1C Unicode byte order indicator + .ReadInt16( m_nPageSize ) // 1E 1 << nPageSize = block size + .ReadInt16( m_nDataPageSize ); // 20 1 << this size == data block size + if (!r.good()) + return false; + if (!checkSeek(r, r.Tell() + 10)) + return false; + r.ReadInt32( m_nFATSize ) // 2C total number of FAT pages + .ReadInt32( m_nTOCstrm ) // 30 starting page for the TOC stream + .ReadInt32( m_nReserved ) // 34 + .ReadInt32( m_nThreshold ) // 38 minimum file size for big data + .ReadInt32( m_nDataFAT ) // 3C page # of 1st data FAT block + .ReadInt32( m_nDataFATSize ) // 40 # of data FATpages + .ReadInt32( m_nMasterChain ) // 44 chain to the next master block + .ReadInt32( m_nMaster ); // 48 # of additional master blocks + for(sal_Int32 & i : m_nMasterFAT) + r.ReadInt32( i ); + + return r.good() && Check(); +} + +bool StgHeader::Store( StgIo& rIo ) +{ + if( !m_bDirty ) + return true; + + SvStream& r = *rIo.GetStrm(); + r.Seek( 0 ); + r.WriteBytes( m_cSignature, 8 ); + WriteClsId( r, m_aClsId ); // 08 Class ID + r.WriteInt32( m_nVersion ) // 1A version number + .WriteUInt16( m_nByteOrder ) // 1C Unicode byte order indicator + .WriteInt16( m_nPageSize ) // 1E 1 << nPageSize = block size + .WriteInt16( m_nDataPageSize ) // 20 1 << this size == data block size + .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt16( 0 ) + .WriteInt32( m_nFATSize ) // 2C total number of FAT pages + .WriteInt32( m_nTOCstrm ) // 30 starting page for the TOC stream + .WriteInt32( m_nReserved ) // 34 + .WriteInt32( m_nThreshold ) // 38 minimum file size for big data + .WriteInt32( m_nDataFAT ) // 3C page # of 1st data FAT block + .WriteInt32( m_nDataFATSize ) // 40 # of data FAT pages + .WriteInt32( m_nMasterChain ) // 44 chain to the next master block + .WriteInt32( m_nMaster ); // 48 # of additional master blocks + for(sal_Int32 i : m_nMasterFAT) + r.WriteInt32( i ); + m_bDirty = sal_uInt8(!rIo.Good()); + return !m_bDirty; +} + +static bool lcl_wontoverflow(short shift) +{ + return shift >= 0 && shift < short(sizeof(short)) * 8 - 1; +} + +static bool isKnownSpecial(sal_Int32 nLocation) +{ + return (nLocation == STG_FREE || + nLocation == STG_EOF || + nLocation == STG_FAT || + nLocation == STG_MASTER); +} + +// Perform thorough checks also on unknown variables +bool StgHeader::Check() +{ + return memcmp( m_cSignature, cStgSignature, 8 ) == 0 + && static_cast<short>( m_nVersion >> 16 ) == 3 + && m_nPageSize == 9 + && lcl_wontoverflow(m_nPageSize) + && lcl_wontoverflow(m_nDataPageSize) + && m_nFATSize > 0 + && m_nTOCstrm >= 0 + && m_nThreshold > 0 + && ( isKnownSpecial(m_nDataFAT) || ( m_nDataFAT >= 0 && m_nDataFATSize > 0 ) ) + && ( isKnownSpecial(m_nMasterChain) || m_nMasterChain >=0 ) + && m_nMaster >= 0; +} + +sal_Int32 StgHeader::GetFATPage( short n ) const +{ + if( n >= 0 && n < cFATPagesInHeader ) + return m_nMasterFAT[ n ]; + else + return STG_EOF; +} + +void StgHeader::SetFATPage( short n, sal_Int32 nb ) +{ + if( n >= 0 && n < cFATPagesInHeader ) + { + if( m_nMasterFAT[ n ] != nb ) + { + m_bDirty = sal_uInt8(true); + m_nMasterFAT[ n ] = nb; + } + } +} + +void StgHeader::SetTOCStart( sal_Int32 n ) +{ + if( n != m_nTOCstrm ) + { + m_bDirty = sal_uInt8(true); + m_nTOCstrm = n; + } +} + +void StgHeader::SetDataFATStart( sal_Int32 n ) +{ + if( n != m_nDataFAT ) + { + m_bDirty = sal_uInt8(true); + m_nDataFAT = n; + } +} + +void StgHeader::SetDataFATSize( sal_Int32 n ) +{ + if( n != m_nDataFATSize ) + { + m_bDirty = sal_uInt8(true); + m_nDataFATSize = n; + } +} + +void StgHeader::SetFATSize( sal_Int32 n ) +{ + if( n != m_nFATSize ) + { + m_bDirty = sal_uInt8(true); + m_nFATSize = n; + } +} + +void StgHeader::SetFATChain( sal_Int32 n ) +{ + if( n != m_nMasterChain ) + { + m_bDirty = sal_uInt8(true); + m_nMasterChain = n; + } +} + +void StgHeader::SetMasters( sal_Int32 n ) +{ + if( n != m_nMaster ) + { + m_bDirty = sal_uInt8(true); + m_nMaster = n; + } +} + +///////////////////////////// class StgEntry + +void StgEntry::Init() +{ + memset( m_nName, 0, sizeof( m_nName ) ); + m_nNameLen = 0; + m_cType = 0; + m_cFlags = 0; + m_nLeft = 0; + m_nRight = 0; + m_nChild = 0; + memset( &m_aClsId, 0, sizeof( m_aClsId ) ); + m_nFlags = 0; + m_nMtime[0] = 0; m_nMtime[1] = 0; + m_nAtime[0] = 0; m_nAtime[1] = 0; + m_nPage1 = 0; + m_nSize = 0; + m_nUnknown = 0; + + SetLeaf( STG_LEFT, STG_FREE ); + SetLeaf( STG_RIGHT, STG_FREE ); + SetLeaf( STG_CHILD, STG_FREE ); + SetLeaf( STG_DATA, STG_EOF ); +} + +static OUString ToUpperUnicode( const OUString & rStr ) +{ + // I don't know the locale, so en_US is hopefully fine + static CharClass aCC( LanguageTag( css::lang::Locale( "en", "US", "" )) ); + return aCC.uppercase( rStr ); +} + +void StgEntry::SetName( const OUString& rName ) +{ + // I don't know the locale, so en_US is hopefully fine + m_aName = ToUpperUnicode( rName ); + if(m_aName.getLength() > nMaxLegalStr) + { + m_aName = m_aName.copy(0, nMaxLegalStr); + } + + sal_Int32 i; + for( i = 0; i < rName.getLength() && i <= nMaxLegalStr; i++ ) + { + m_nName[ i ] = rName[ i ]; + } + while (i <= nMaxLegalStr) + { + m_nName[ i++ ] = 0; + } + m_nNameLen = ( rName.getLength() + 1 ) << 1; +} + +sal_Int32 StgEntry::GetLeaf( StgEntryRef eRef ) const +{ + sal_Int32 n = -1; + switch( eRef ) + { + case STG_LEFT: n = m_nLeft; break; + case STG_RIGHT: n = m_nRight; break; + case STG_CHILD: n = m_nChild; break; + case STG_DATA: n = m_nPage1; break; + } + return n; +} + +void StgEntry::SetLeaf( StgEntryRef eRef, sal_Int32 nPage ) +{ + switch( eRef ) + { + case STG_LEFT: m_nLeft = nPage; break; + case STG_RIGHT: m_nRight = nPage; break; + case STG_CHILD: m_nChild = nPage; break; + case STG_DATA: m_nPage1 = nPage; break; + } +} + +void StgEntry::SetClassId( const ClsId& r ) +{ + memcpy( &m_aClsId, &r, sizeof( ClsId ) ); +} + +void StgEntry::GetName( OUString& rName ) const +{ + sal_uInt16 n = m_nNameLen; + if( n ) + n = ( n >> 1 ) - 1; + rName = OUString(m_nName, n); +} + +// Compare two entries. Do this case-insensitive. + +sal_Int32 StgEntry::Compare( const StgEntry& r ) const +{ + if (r.m_nNameLen != m_nNameLen) + return r.m_nNameLen > m_nNameLen ? 1 : -1; + else + return r.m_aName.compareTo(m_aName); +} + +// These load/store operations are a bit more complicated, +// since they have to copy their contents into a packed structure. + +bool StgEntry::Load(const void* pFrom, sal_uInt32 nBufSize, sal_uInt64 nUnderlyingStreamSize) +{ + if ( nBufSize < 128 ) + return false; + + SvMemoryStream r( const_cast<void *>(pFrom), nBufSize, StreamMode::READ ); + for(sal_Unicode & i : m_nName) + r.ReadUtf16( i ); // 00 name as WCHAR + r.ReadUInt16( m_nNameLen ) // 40 size of name in bytes including 00H + .ReadUChar( m_cType ) // 42 entry type + .ReadUChar( m_cFlags ) // 43 0 or 1 (tree balance?) + .ReadInt32( m_nLeft ) // 44 left node entry + .ReadInt32( m_nRight ) // 48 right node entry + .ReadInt32( m_nChild ); // 4C 1st child entry if storage + ReadClsId( r, m_aClsId ); // 50 class ID (optional) + r.ReadInt32( m_nFlags ) // 60 state flags(?) + .ReadInt32( m_nMtime[ 0 ] ) // 64 modification time + .ReadInt32( m_nMtime[ 1 ] ) // 64 modification time + .ReadInt32( m_nAtime[ 0 ] ) // 6C creation and access time + .ReadInt32( m_nAtime[ 1 ] ) // 6C creation and access time + .ReadInt32( m_nPage1 ) // 74 starting block (either direct or translated) + .ReadInt32( m_nSize ) // 78 file size + .ReadInt32( m_nUnknown ); // 7C unknown + + sal_uInt16 n = m_nNameLen; + if( n ) + n = ( n >> 1 ) - 1; + + if (n > nMaxLegalStr) + return false; + + if (m_cType != STG_STORAGE) + { + if (m_nPage1 < 0 && !isKnownSpecial(m_nPage1)) + { + //bad pageid + return false; + } + if (m_cType == STG_EMPTY) + { + /* + tdf#112399 opens fine in MSOffice 2013 despite a massive m_nSize field + + Free (unused) directory entries are marked with Object Type 0x0 + (unknown or unallocated). The entire directory entry must consist of + all zeroes except for the child, right sibling, and left sibling + pointers, which must be initialized to NOSTREAM (0xFFFFFFFF). + */ + m_nSize = 0; + } + if (m_nSize < 0) + { + // the size makes no sense for the substorage + // TODO/LATER: actually the size should be an unsigned value, but + // in this case it would mean a stream of more than 2Gb + return false; + } + if (o3tl::make_unsigned(m_nSize) > nUnderlyingStreamSize) + { + // surely an entry cannot be larger than the underlying file + return false; + } + + } + + m_aName = OUString(m_nName , n); + // I don't know the locale, so en_US is hopefully fine + m_aName = ToUpperUnicode( m_aName ); + if(m_aName.getLength() > nMaxLegalStr) + { + m_aName = m_aName.copy(0, nMaxLegalStr); + } + + return true; +} + +void StgEntry::Store( void* pTo ) +{ + SvMemoryStream r( pTo, 128, StreamMode::WRITE ); + for(sal_Unicode i : m_nName) + r.WriteUInt16( i ); // 00 name as WCHAR + r.WriteUInt16( m_nNameLen ) // 40 size of name in bytes including 00H + .WriteUChar( m_cType ) // 42 entry type + .WriteUChar( m_cFlags ) // 43 0 or 1 (tree balance?) + .WriteInt32( m_nLeft ) // 44 left node entry + .WriteInt32( m_nRight ) // 48 right node entry + .WriteInt32( m_nChild ); // 4C 1st child entry if storage; + WriteClsId( r, m_aClsId ); // 50 class ID (optional) + r.WriteInt32( m_nFlags ) // 60 state flags(?) + .WriteInt32( m_nMtime[ 0 ] ) // 64 modification time + .WriteInt32( m_nMtime[ 1 ] ) // 64 modification time + .WriteInt32( m_nAtime[ 0 ] ) // 6C creation and access time + .WriteInt32( m_nAtime[ 1 ] ) // 6C creation and access time + .WriteInt32( m_nPage1 ) // 74 starting block (either direct or translated) + .WriteInt32( m_nSize ) // 78 file size + .WriteInt32( m_nUnknown ); // 7C unknown +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgelem.hxx b/sot/source/sdstor/stgelem.hxx new file mode 100644 index 000000000..855b2b879 --- /dev/null +++ b/sot/source/sdstor/stgelem.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ + +// This file reflects the structure of MS file elements. +// It is very sensitive to alignment! + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGELEM_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGELEM_HXX + +#include <sot/stg.hxx> + +class StgIo; +class SvStream; + +SvStream& ReadClsId( SvStream&, ClsId& ); +SvStream& WriteClsId( SvStream&, const ClsId& ); + +class StgHeader +{ + static const sal_uInt8 cFATPagesInHeader = 109; + + sal_uInt8 m_cSignature[ 8 ] = {}; // 00 signature (see below) + ClsId m_aClsId = {}; // 08 Class ID + sal_Int32 m_nVersion; // 18 version number + sal_uInt16 m_nByteOrder; // 1C Unicode byte order indicator + sal_Int16 m_nPageSize; // 1E 1 << nPageSize = block size + sal_Int16 m_nDataPageSize; // 20 1 << this size == data block size + sal_uInt8 m_bDirty; // 22 internal dirty flag (should be + // bool, but probably required to + // be exactly one byte) + sal_uInt8 m_cReserved[ 9 ] = {}; // 23 + sal_Int32 m_nFATSize; // 2C total number of FAT pages + sal_Int32 m_nTOCstrm; // 30 starting page for the TOC stream + sal_Int32 m_nReserved; // 34 + sal_Int32 m_nThreshold; // 38 minimum file size for big data + sal_Int32 m_nDataFAT; // 3C page # of 1st data FAT block + sal_Int32 m_nDataFATSize; // 40 # of data fat blocks + sal_Int32 m_nMasterChain; // 44 chain to the next master block + sal_Int32 m_nMaster; // 48 # of additional master blocks + sal_Int32 m_nMasterFAT[ cFATPagesInHeader ] = {}; // 4C first [cFATPagesInHeader] master FAT pages +public: + StgHeader(); + + void Init(); // initialize the header + bool Load( StgIo& ); + bool Load( SvStream& ); + bool Store( StgIo& ); + bool Check(); // check the signature and version + sal_Int32 GetTOCStart() const { return m_nTOCstrm; } + void SetTOCStart( sal_Int32 n ); + sal_Int32 GetDataFATStart() const { return m_nDataFAT; } + void SetDataFATStart( sal_Int32 n ); + sal_Int32 GetDataFATSize() const { return m_nDataFATSize; } + void SetDataFATSize( sal_Int32 n ); + sal_Int32 GetThreshold() const { return m_nThreshold; } + short GetPageSize() const { return m_nPageSize; } + short GetDataPageSize() const { return m_nDataPageSize; } + sal_Int32 GetFATSize() const { return m_nFATSize; } + void SetFATSize( sal_Int32 n ); + sal_Int32 GetFATChain() const { return m_nMasterChain; } + void SetFATChain( sal_Int32 n ); + sal_Int32 GetMasters() const { return m_nMaster; } + void SetMasters( sal_Int32 n ); + static short GetFAT1Size() { return cFATPagesInHeader; } + sal_Int32 GetFATPage( short ) const; + void SetFATPage( short, sal_Int32 ); +}; + +enum StgEntryType { // dir entry types: + STG_EMPTY = 0, + STG_STORAGE = 1, + STG_STREAM = 2, + STG_ROOT = 5 +}; + +enum StgEntryRef { // reference blocks: + STG_LEFT = 0, // left + STG_RIGHT = 1, // right + STG_CHILD = 2, // child + STG_DATA = 3 // data start +}; + +#define STGENTRY_SIZE 128 + +//StructuredStorageDirectoryEntry +class StgEntry +{ // directory entry + sal_Unicode m_nName[ 32 ]; // 00 name as WCHAR + sal_uInt16 m_nNameLen; // 40 size of name in bytes including 00H + sal_uInt8 m_cType; // 42 entry type + sal_uInt8 m_cFlags; // 43 0 or 1 (tree balance?) + sal_Int32 m_nLeft; // 44 left node entry + sal_Int32 m_nRight; // 48 right node entry + sal_Int32 m_nChild; // 4C 1st child entry if storage + ClsId m_aClsId; // 50 class ID (optional) + sal_Int32 m_nFlags; // 60 state flags(?) + sal_Int32 m_nMtime[ 2 ]; // 64 modification time + sal_Int32 m_nAtime[ 2 ]; // 6C creation and access time + sal_Int32 m_nPage1; // 74 starting block (either direct or translated) + sal_Int32 m_nSize; // 78 file size + sal_Int32 m_nUnknown; // 7C unknown + OUString m_aName; // Name as Compare String (ascii, upper) +public: + void Init(); // initialize the data + void SetName( const OUString& ); // store a name (ASCII, up to 32 chars) + void GetName( OUString& rName ) const; + // fill in the name + sal_Int32 Compare( const StgEntry& ) const; // compare two entries + bool Load( const void* pBuffer, sal_uInt32 nBufSize, sal_uInt64 nUnderlyingStreamSize ); + void Store( void* ); + StgEntryType GetType() const { return static_cast<StgEntryType>(m_cType); } + sal_Int32 GetStartPage() const { return m_nPage1; } + void SetType( StgEntryType t ) { m_cType = static_cast<sal_uInt8>(t); } + sal_Int32 GetSize() const { return m_nSize; } + void SetSize( sal_Int32 n ) { m_nSize = n; } + const ClsId& GetClassId() const { return m_aClsId; } + void SetClassId( const ClsId& ); + sal_Int32 GetLeaf( StgEntryRef ) const; + void SetLeaf( StgEntryRef, sal_Int32 ); +}; + + +#define STG_FREE -1L // page is free +#define STG_EOF -2L // page is last page in chain +#define STG_FAT -3L // page is FAT page +#define STG_MASTER -4L // page is master FAT page + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgio.cxx b/sot/source/sdstor/stgio.cxx new file mode 100644 index 000000000..604d08282 --- /dev/null +++ b/sot/source/sdstor/stgio.cxx @@ -0,0 +1,400 @@ +/* -*- 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 "stgelem.hxx" +#include "stgcache.hxx" +#include "stgstrms.hxx" +#include "stgdir.hxx" +#include "stgio.hxx" +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <memory> +#include <optional> + +///////////////////////////// class StgIo + +// This class holds the storage header and all internal streams. + +StgIo::StgIo() +{ + m_pTOC = nullptr; + m_pDataFAT = nullptr; + m_pDataStrm = nullptr; + m_pFAT = nullptr; + m_bCopied = false; +} + +StgIo::~StgIo() +{ + delete m_pTOC; + delete m_pDataFAT; + delete m_pDataStrm; + delete m_pFAT; +} + +// Load the header. Do not set an error code if the header is invalid. + +bool StgIo::Load() +{ + if( GetStrm() ) + { + if( m_aHdr.Load( *this ) ) + { + if( m_aHdr.Check() ) + SetupStreams(); + else + return false; + } + else + return false; + } + return Good(); +} + +// Set up an initial, empty storage + +bool StgIo::Init() +{ + m_aHdr.Init(); + SetupStreams(); + return CommitAll(); +} + +void StgIo::SetupStreams() +{ + delete m_pTOC; + delete m_pDataFAT; + delete m_pDataStrm; + delete m_pFAT; + m_pTOC = nullptr; + m_pDataFAT = nullptr; + m_pDataStrm = nullptr; + m_pFAT = nullptr; + ResetError(); + + short nPhysPageSize = 1 << m_aHdr.GetPageSize(); + SetPhysPageSize(nPhysPageSize); + sal_Int32 nFatStrmSize; + if (o3tl::checked_multiply<sal_Int32>(m_aHdr.GetFATSize(), nPhysPageSize, nFatStrmSize)) + { + SAL_WARN("sot", "Error: " << m_aHdr.GetFATSize() << " * " << nPhysPageSize << " would overflow"); + SetError(SVSTREAM_FILEFORMAT_ERROR); + m_pFAT = nullptr; + m_pTOC = nullptr; + return; + } + + m_pFAT = new StgFATStrm(*this, nFatStrmSize); + m_pTOC = new StgDirStrm(*this); + if( GetError() ) + return; + + StgDirEntry* pRoot = m_pTOC->GetRoot(); + if( pRoot ) + { + m_pDataFAT = new StgDataStrm( *this, m_aHdr.GetDataFATStart(), -1 ); + m_pDataStrm = new StgDataStrm( *this, *pRoot ); + m_pDataFAT->SetIncrement( 1 << m_aHdr.GetPageSize() ); + m_pDataStrm->SetIncrement( GetDataPageSize() ); + m_pDataStrm->SetEntry( *pRoot ); + } + else + SetError( SVSTREAM_FILEFORMAT_ERROR ); +} + +// get the logical data page size + +short StgIo::GetDataPageSize() const +{ + return 1 << m_aHdr.GetDataPageSize(); +} + +// Commit everything + +bool StgIo::CommitAll() +{ + // Store the data (all streams and the TOC) + if( m_pTOC && m_pTOC->Store() && m_pDataFAT ) + { + if( Commit() ) + { + m_aHdr.SetDataFATStart( m_pDataFAT->GetStart() ); + m_aHdr.SetDataFATSize( m_pDataFAT->GetPages() ); + m_aHdr.SetTOCStart( m_pTOC->GetStart() ); + if( m_aHdr.Store( *this ) ) + { + GetStrm()->Flush(); + const ErrCode n = GetStrm()->GetError(); + SetError( n ); +#ifdef DBG_UTIL + if( n==ERRCODE_NONE ) ValidateFATs(); +#endif + return n == ERRCODE_NONE; + } + } + } + SetError( SVSTREAM_WRITE_ERROR ); + return false; +} + +namespace { + +class EasyFat +{ + std::unique_ptr<sal_Int32[]> pFat; + std::unique_ptr<bool[]> pFree; + sal_Int32 nPages; + sal_Int32 nPageSize; + +public: + EasyFat( StgIo & rIo, StgStrm *pFatStream, sal_Int32 nPSize ); + + sal_Int32 GetPageSize() const { return nPageSize; } + + FatError Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect ); + bool HasUnrefChains() const; +}; + +} + +EasyFat::EasyFat( StgIo& rIo, StgStrm* pFatStream, sal_Int32 nPSize ) + : nPages(pFatStream->GetSize() >> 2), nPageSize(nPSize) +{ + pFat.reset( new sal_Int32[ nPages ] ); + pFree.reset( new bool[ nPages ] ); + + rtl::Reference< StgPage > pPage; + sal_Int32 nFatPageSize = (1 << rIo.m_aHdr.GetPageSize()) - 2; + + for( sal_Int32 nPage = 0; nPage < nPages; nPage++ ) + { + if( ! (nPage % nFatPageSize) ) + { + pFatStream->Pos2Page( nPage << 2 ); + sal_Int32 nPhysPage = pFatStream->GetPage(); + pPage = rIo.Get( nPhysPage, true ); + } + + pFat[ nPage ] = StgCache::GetFromPage( pPage, short( nPage % nFatPageSize ) ); + pFree[ nPage ] = true; + } +} + +bool EasyFat::HasUnrefChains() const +{ + for( sal_Int32 nPage = 0; nPage < nPages; nPage++ ) + { + if( pFree[ nPage ] && pFat[ nPage ] != -1 ) + return true; + } + return false; +} + +FatError EasyFat::Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect ) +{ + if( nCount > 0 ) + { + --nCount; + nCount /= GetPageSize(); + ++nCount; + } + + sal_Int32 nCurPage = nPage; + while( nCount != 0 ) + { + if( nCurPage < 0 || nCurPage >= nPages ) + return FatError::OutOfBounds; + pFree[ nCurPage ] = false; + nCurPage = pFat[ nCurPage ]; + // stream too long + if( nCurPage != nExpect && nCount == 1 ) + return FatError::WrongLength; + // stream too short + if( nCurPage == nExpect && nCount != 1 && nCount != -1 ) + return FatError::WrongLength; + // last block for stream without length + if( nCurPage == nExpect && nCount == -1 ) + nCount = 1; + if( nCount != -1 ) + nCount--; + } + return FatError::Ok; +} + +namespace { + +class Validator +{ + FatError nError; + + EasyFat aSmallFat; + EasyFat aFat; + + StgIo &rIo; + + FatError ValidateMasterFATs(); + FatError ValidateDirectoryEntries(); + FatError FindUnrefedChains() const; + FatError MarkAll( StgDirEntry *pEntry ); + +public: + explicit Validator( StgIo &rIo ); + bool IsError() const { return nError != FatError::Ok; } +}; + +} + +Validator::Validator( StgIo &rIoP ) + : aSmallFat( rIoP, rIoP.m_pDataFAT, 1 << rIoP.m_aHdr.GetDataPageSize() ), + aFat( rIoP, rIoP.m_pFAT, 1 << rIoP.m_aHdr.GetPageSize() ), + rIo( rIoP ) +{ + FatError nErr = nError = FatError::Ok; + + if( ( nErr = ValidateMasterFATs() ) != FatError::Ok ) + nError = nErr; + else if( ( nErr = ValidateDirectoryEntries() ) != FatError::Ok ) + nError = nErr; + else if( ( nErr = FindUnrefedChains()) != FatError::Ok ) + nError = nErr; +} + +FatError Validator::ValidateMasterFATs() +{ + sal_Int32 nCount = rIo.m_aHdr.GetFATSize(); + FatError nErr; + if ( !rIo.m_pFAT ) + return FatError::InMemoryError; + + for( sal_Int32 i = 0; i < nCount; i++ ) + { + if( ( nErr = aFat.Mark(rIo.m_pFAT->GetPage(i, false), aFat.GetPageSize(), -3 )) != FatError::Ok) + return nErr; + } + if( rIo.m_aHdr.GetMasters() ) + if( ( nErr = aFat.Mark(rIo.m_aHdr.GetFATChain( ), aFat.GetPageSize(), -4 )) != FatError::Ok ) + return nErr; + + return FatError::Ok; +} + +FatError Validator::MarkAll( StgDirEntry *pEntry ) +{ + if ( !pEntry ) + return FatError::InMemoryError; + + StgIterator aIter( *pEntry ); + FatError nErr = FatError::Ok; + for( StgDirEntry* p = aIter.First(); p ; p = aIter.Next() ) + { + if( p->m_aEntry.GetType() == STG_STORAGE ) + { + nErr = MarkAll( p ); + if( nErr != FatError::Ok ) + return nErr; + } + else + { + sal_Int32 nSize = p->m_aEntry.GetSize(); + if( nSize < rIo.m_aHdr.GetThreshold() ) + nErr = aSmallFat.Mark( p->m_aEntry.GetStartPage(),nSize, -2 ); + else + nErr = aFat.Mark( p->m_aEntry.GetStartPage(),nSize, -2 ); + if( nErr != FatError::Ok ) + return nErr; + } + } + return FatError::Ok; +} + +FatError Validator::ValidateDirectoryEntries() +{ + if ( !rIo.m_pTOC ) + return FatError::InMemoryError; + + // Normal DirEntries + FatError nErr = MarkAll( rIo.m_pTOC->GetRoot() ); + if( nErr != FatError::Ok ) + return nErr; + // Small Data + nErr = aFat.Mark( rIo.m_pTOC->GetRoot()->m_aEntry.GetStartPage(), + rIo.m_pTOC->GetRoot()->m_aEntry.GetSize(), -2 ); + if( nErr != FatError::Ok ) + return nErr; + // Small Data FAT + nErr = aFat.Mark( + rIo.m_aHdr.GetDataFATStart(), + rIo.m_aHdr.GetDataFATSize() * aFat.GetPageSize(), -2 ); + if( nErr != FatError::Ok ) + return nErr; + // TOC + nErr = aFat.Mark( + rIo.m_aHdr.GetTOCStart(), -1, -2 ); + return nErr; +} + +FatError Validator::FindUnrefedChains() const +{ + if( aSmallFat.HasUnrefChains() || + aFat.HasUnrefChains() ) + return FatError::UnrefChain; + else + return FatError::Ok; +} + +FatError StgIo::ValidateFATs() +{ + if( m_bFile ) + { + std::optional<Validator> pV( *this ); + bool bRet1 = !pV->IsError(), bRet2 = true ; + pV.reset(); + + SvFileStream *pFileStrm = static_cast<SvFileStream *>( GetStrm() ); + if ( !pFileStrm ) + return FatError::InMemoryError; + + StgIo aIo; + if( aIo.Open( pFileStrm->GetFileName(), + StreamMode::READ | StreamMode::SHARE_DENYNONE) && + aIo.Load() ) + { + pV.emplace( aIo ); + bRet2 = !pV->IsError(); + pV.reset(); + } + + FatError nErr; + if( bRet1 != bRet2 ) + nErr = bRet1 ? FatError::OnFileError : FatError::InMemoryError; + else nErr = bRet1 ? FatError::Ok : FatError::BothError; + if( nErr != FatError::Ok && !m_bCopied ) + { + m_bCopied = true; + } +// DBG_ASSERT( nErr == FatError::Ok ,"Storage broken"); + return nErr; + } +// OSL_FAIL("Do not validate (no FileStorage)"); + return FatError::Ok; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgio.hxx b/sot/source/sdstor/stgio.hxx new file mode 100644 index 000000000..7ed5917ac --- /dev/null +++ b/sot/source/sdstor/stgio.hxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGIO_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGIO_HXX + +#include "stgcache.hxx" +#include "stgelem.hxx" + +class StgFATStrm; +class StgDataStrm; +class StgDirStrm; + +enum class FatError +{ + Ok, + WrongLength, + UnrefChain, + OutOfBounds, + + InMemoryError, + OnFileError, + BothError +}; + +class StgIo : public StgCache { + void SetupStreams(); // load all internal streams + bool m_bCopied; +public: + StgIo(); + ~StgIo(); + StgHeader m_aHdr; // storage file header + StgFATStrm* m_pFAT; // FAT stream + StgDirStrm* m_pTOC; // TOC stream + StgDataStrm* m_pDataFAT; // small data FAT stream + StgDataStrm* m_pDataStrm; // small data stream + short GetDataPageSize() const; // get the logical data page size + bool Load(); // load a storage file + bool Init(); // set up an empty file + bool CommitAll(); // commit everything (root commit) + + FatError ValidateFATs( ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgole.cxx b/sot/source/sdstor/stgole.cxx new file mode 100644 index 000000000..ac23f7a14 --- /dev/null +++ b/sot/source/sdstor/stgole.cxx @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <algorithm> + +#include "stgelem.hxx" +#include "stgole.hxx" +#include <o3tl/safeint.hxx> +#include <sot/storinfo.hxx> + +///////////////////////// class StgInternalStream + +StgInternalStream::StgInternalStream( BaseStorage& rStg, const OUString& rName, bool bWr ) +{ + m_isWritable = true; + StreamMode nMode = bWr + ? StreamMode::WRITE | StreamMode::SHARE_DENYALL + : StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE; + m_pStrm.reset( rStg.OpenStream( rName, nMode ) ); + + // set the error code right here in the stream + SetError( rStg.GetError() ); + SetBufferSize( 1024 ); +} + +StgInternalStream::~StgInternalStream() +{ +} + +std::size_t StgInternalStream::GetData(void* pData, std::size_t nSize) +{ + if( m_pStrm ) + { + nSize = m_pStrm->Read( pData, nSize ); + SetError( m_pStrm->GetError() ); + return nSize; + } + else + return 0; +} + +std::size_t StgInternalStream::PutData(const void* pData, std::size_t nSize) +{ + if( m_pStrm ) + { + nSize = m_pStrm->Write( pData, nSize ); + SetError( m_pStrm->GetError() ); + return nSize; + } + else + return 0; +} + +sal_uInt64 StgInternalStream::SeekPos(sal_uInt64 const nPos) +{ + return m_pStrm ? m_pStrm->Seek( nPos ) : 0; +} + +void StgInternalStream::FlushData() +{ + if( m_pStrm ) + { + m_pStrm->Flush(); + SetError( m_pStrm->GetError() ); + } +} + +void StgInternalStream::Commit() +{ + Flush(); + m_pStrm->Commit(); +} + +///////////////////////// class StgCompObjStream + +StgCompObjStream::StgCompObjStream( BaseStorage& rStg, bool bWr ) + : StgInternalStream( rStg, "\1CompObj", bWr ) +{ +} + +bool StgCompObjStream::Load() +{ + memset( &m_aClsId, 0, sizeof( ClsId ) ); + m_nCbFormat = SotClipboardFormatId::NONE; + m_aUserName.clear(); + if( GetError() != ERRCODE_NONE ) + return false; + Seek( 8 ); // skip the first part + sal_Int32 nMarker = 0; + ReadInt32( nMarker ); + if( nMarker == -1 ) + { + ReadClsId( *this, m_aClsId ); + sal_Int32 nLen1 = 0; + ReadInt32( nLen1 ); + if ( nLen1 > 0 ) + { + // higher bits are ignored + sal_Int32 nStrLen = ::std::min( nLen1, sal_Int32(0xFFFE) ); + + std::unique_ptr<char[]> p(new char[ nStrLen+1 ]); + p[nStrLen] = 0; + if (ReadBytes( p.get(), nStrLen ) == o3tl::make_unsigned(nStrLen)) + { + //The encoding here is "ANSI", which is pretty useless seeing as + //the actual codepage used doesn't seem to be specified/stored + //anywhere :-(. Might as well pick 1252 and be consistent on + //all platforms and envs + //https://bz.apache.org/ooo/attachment.cgi?id=68668 + //for a good edge-case example + m_aUserName = OUString(p.get(), nStrLen, RTL_TEXTENCODING_MS_1252); + m_nCbFormat = ReadClipboardFormat( *this ); + } + else + SetError( SVSTREAM_GENERALERROR ); + } + } + return GetError() == ERRCODE_NONE; +} + +bool StgCompObjStream::Store() +{ + if( GetError() != ERRCODE_NONE ) + return false; + Seek( 0 ); + OString aAsciiUserName(OUStringToOString(m_aUserName, RTL_TEXTENCODING_MS_1252)); + WriteInt16( 1 ); // Version? + WriteInt16( -2 ); // 0xFFFE = Byte Order Indicator + WriteInt32( 0x0A03 ); // Windows 3.10 + WriteInt32( -1 ); + WriteClsId( *this, m_aClsId ); // Class ID + WriteInt32( aAsciiUserName.getLength() + 1 ); + WriteOString( aAsciiUserName ); + WriteUChar( 0 ); // string terminator + WriteClipboardFormat( *this, m_nCbFormat ); + WriteInt32( 0 ); // terminator + Commit(); + return GetError() == ERRCODE_NONE; +} + +/////////////////////////// class StgOleStream + +StgOleStream::StgOleStream( BaseStorage& rStg ) + : StgInternalStream( rStg, "\1Ole", true ) +{ +} + +bool StgOleStream::Store() +{ + if( GetError() != ERRCODE_NONE ) + return false; + + Seek( 0 ); + WriteInt32( 0x02000001 ); // OLE version, format + WriteInt32( 0 ); // Object flags + WriteInt32( 0 ); // Update Options + WriteInt32( 0 ); // reserved + WriteInt32( 0 ); // Moniker 1 + Commit(); + return GetError() == ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgole.hxx b/sot/source/sdstor/stgole.hxx new file mode 100644 index 000000000..899b07fcd --- /dev/null +++ b/sot/source/sdstor/stgole.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGOLE_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGOLE_HXX + +#include <memory> + +#include <sot/stg.hxx> + +class StgInternalStream : public SvStream +{ + std::unique_ptr<BaseStorageStream> m_pStrm; + virtual std::size_t GetData(void* pData, std::size_t nSize) override; + virtual std::size_t PutData(const void* pData, std::size_t nSize) override; + virtual sal_uInt64 SeekPos( sal_uInt64 nPos ) override; + virtual void FlushData() override; +public: + StgInternalStream( BaseStorage&, const OUString&, bool ); + virtual ~StgInternalStream() override; + void Commit(); +}; + +// standard stream "\1CompObj" + +class StgCompObjStream : public StgInternalStream +{ + ClsId m_aClsId = {}; + OUString m_aUserName; + SotClipboardFormatId m_nCbFormat = SotClipboardFormatId::NONE; +public: + StgCompObjStream( BaseStorage&, bool ); + ClsId& GetClsId() { return m_aClsId; } + OUString& GetUserName() { return m_aUserName; } + SotClipboardFormatId& GetCbFormat() { return m_nCbFormat; } + bool Load(); + bool Store(); +}; + +// standard stream "\1Ole" + +class StgOleStream : public StgInternalStream +{ +public: + explicit StgOleStream( BaseStorage& ); + bool Store(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgstrms.cxx b/sot/source/sdstor/stgstrms.cxx new file mode 100644 index 000000000..717e4e7d1 --- /dev/null +++ b/sot/source/sdstor/stgstrms.cxx @@ -0,0 +1,1356 @@ +/* -*- 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 <algorithm> + +#include <string.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> +#include <unotools/tempfile.hxx> + +#include "stgelem.hxx" +#include "stgcache.hxx" +#include "stgstrms.hxx" +#include "stgdir.hxx" +#include "stgio.hxx" +#include <memory> + +///////////////////////////// class StgFAT + +// The FAT class performs FAT operations on an underlying storage stream. +// This stream is either the master FAT stream (m == true ) or a normal +// storage stream, which then holds the FAT for small data allocations. + +StgFAT::StgFAT( StgStrm& r, bool m ) : m_rStrm( r ) +{ + m_bPhys = m; + m_nPageSize = m_rStrm.GetIo().GetPhysPageSize(); + m_nEntries = m_nPageSize >> 2; + m_nOffset = 0; + m_nMaxPage = 0; + m_nLimit = 0; +} + +// Retrieve the physical page for a given byte offset. + +rtl::Reference< StgPage > StgFAT::GetPhysPage( sal_Int32 nByteOff ) +{ + rtl::Reference< StgPage > pPg; + // Position within the underlying stream + // use the Pos2Page() method of the stream + if( m_rStrm.Pos2Page( nByteOff ) ) + { + m_nOffset = m_rStrm.GetOffset(); + sal_Int32 nPhysPage = m_rStrm.GetPage(); + // get the physical page (must be present) + pPg = m_rStrm.GetIo().Get( nPhysPage, true ); + } + return pPg; +} + +// Get the follow page for a certain FAT page. + +sal_Int32 StgFAT::GetNextPage( sal_Int32 nPg ) +{ + if (nPg >= 0) + { + if (nPg > (SAL_MAX_INT32 >> 2)) + return STG_EOF; + rtl::Reference< StgPage > pPg = GetPhysPage( nPg << 2 ); + nPg = pPg.is() ? StgCache::GetFromPage( pPg, m_nOffset >> 2 ) : STG_EOF; + } + return nPg; +} + +// Find the best fit block for the given size. Return +// the starting block and its size or STG_EOF and 0. +// nLastPage is a stopper which tells the current +// underlying stream size. It is treated as a recommendation +// to abort the search to inhibit excessive file growth. + +sal_Int32 StgFAT::FindBlock( sal_Int32& nPgs ) +{ + sal_Int32 nMinStart = STG_EOF, nMinLen = 0; + sal_Int32 nMaxStart = STG_EOF, nMaxLen = 0x7FFFFFFFL; + sal_Int32 nTmpStart = STG_EOF, nTmpLen = 0; + sal_Int32 nPages = m_rStrm.GetSize() >> 2; + bool bFound = false; + rtl::Reference< StgPage > pPg; + short nEntry = 0; + for( sal_Int32 i = 0; i < nPages; i++, nEntry++ ) + { + if( !( nEntry % m_nEntries ) ) + { + // load the next page for that stream + nEntry = 0; + pPg = GetPhysPage( i << 2 ); + if( !pPg.is() ) + return STG_EOF; + } + sal_Int32 nCur = StgCache::GetFromPage( pPg, nEntry ); + if( nCur == STG_FREE ) + { + // count the size of this area + if( nTmpLen ) + nTmpLen++; + else + { + nTmpStart = i; + nTmpLen = 1; + } + if( nTmpLen == nPgs + // If we already did find a block, stop when reaching the limit + || ( bFound && ( nEntry >= m_nLimit ) ) ) + break; + } + else if( nTmpLen ) + { + if( nTmpLen > nPgs && nTmpLen < nMaxLen ) + { + // block > requested size + nMaxLen = nTmpLen; + nMaxStart = nTmpStart; + bFound = true; + } + else if( nTmpLen >= nMinLen ) + { + // block < requested size + nMinLen = nTmpLen; + nMinStart = nTmpStart; + bFound = true; + if( nTmpLen == nPgs ) + break; + } + nTmpStart = STG_EOF; + nTmpLen = 0; + } + } + // Determine which block to use. + if( nTmpLen ) + { + if( nTmpLen > nPgs && nTmpLen < nMaxLen ) + { + // block > requested size + nMaxLen = nTmpLen; + nMaxStart = nTmpStart; + } + else if( nTmpLen >= nMinLen ) + { + // block < requested size + nMinLen = nTmpLen; + nMinStart = nTmpStart; + } + } + if( nMinStart != STG_EOF && nMaxStart != STG_EOF ) + { + // two areas found; return the best fit area + sal_Int32 nMinDiff = nPgs - nMinLen; + sal_Int32 nMaxDiff = nMaxLen - nPgs; + if( nMinDiff > nMaxDiff ) + nMinStart = STG_EOF; + } + if( nMinStart != STG_EOF ) + { + nPgs = nMinLen; return nMinStart; + } + else + { + return nMaxStart; + } +} + +// Set up the consecutive chain for a given block. + +bool StgFAT::MakeChain( sal_Int32 nStart, sal_Int32 nPgs ) +{ + sal_Int32 nPos = nStart << 2; + rtl::Reference< StgPage > pPg = GetPhysPage( nPos ); + if( !pPg.is() || !nPgs ) + return false; + while( --nPgs ) + { + if( m_nOffset >= m_nPageSize ) + { + pPg = GetPhysPage( nPos ); + if( !pPg.is() ) + return false; + } + m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, ++nStart ); + m_nOffset += 4; + nPos += 4; + } + if( m_nOffset >= m_nPageSize ) + { + pPg = GetPhysPage( nPos ); + if( !pPg.is() ) + return false; + } + m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, STG_EOF ); + return true; +} + +// Allocate a block of data from the given page number on. +// It the page number is != STG_EOF, chain the block. + +sal_Int32 StgFAT::AllocPages( sal_Int32 nBgn, sal_Int32 nPgs ) +{ + sal_Int32 nOrig = nBgn; + sal_Int32 nLast = nBgn; + sal_Int32 nBegin = STG_EOF; + sal_Int32 nAlloc; + sal_Int32 nPages = m_rStrm.GetSize() >> 2; + short nPasses = 0; + // allow for two passes + while( nPasses < 2 ) + { + // try to satisfy the request from the pool of free pages + while( nPgs ) + { + nAlloc = nPgs; + nBegin = FindBlock( nAlloc ); + // no more blocks left in present alloc chain + if( nBegin == STG_EOF ) + break; + if( ( nBegin + nAlloc ) > m_nMaxPage ) + m_nMaxPage = nBegin + nAlloc; + if( !MakeChain( nBegin, nAlloc ) ) + return STG_EOF; + if( nOrig == STG_EOF ) + nOrig = nBegin; + else + { + // Patch the chain + rtl::Reference< StgPage > pPg = GetPhysPage( nLast << 2 ); + if( !pPg.is() ) + return STG_EOF; + m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, nBegin ); + } + nLast = nBegin + nAlloc - 1; + nPgs -= nAlloc; + } + if( nPgs && !nPasses ) + { + // we need new, fresh space, so allocate and retry + if( !m_rStrm.SetSize( ( nPages + nPgs ) << 2 ) ) + return STG_EOF; + if( !m_bPhys && !InitNew( nPages ) ) + return 0; + // FIXME: this was originally "FALSE", whether or not that + // makes sense (or should be STG_EOF instead, say?) + nPages = m_rStrm.GetSize() >> 2; + nPasses++; + } + else + break; + } + // now we should have a chain for the complete block + if( nBegin == STG_EOF || nPgs ) + { + m_rStrm.GetIo().SetError( SVSTREAM_FILEFORMAT_ERROR ); + return STG_EOF; // bad structure + } + return nOrig; +} + +// Initialize newly allocated pages for a standard FAT stream +// It can be assumed that the stream size is always on +// a page boundary + +bool StgFAT::InitNew( sal_Int32 nPage1 ) +{ + sal_Int32 n = ( ( m_rStrm.GetSize() >> 2 ) - nPage1 ) / m_nEntries; + if ( n > 0 ) + { + while( n-- ) + { + rtl::Reference< StgPage > pPg; + // Position within the underlying stream + // use the Pos2Page() method of the stream + m_rStrm.Pos2Page( nPage1 << 2 ); + // Initialize the page + pPg = m_rStrm.GetIo().Copy( m_rStrm.GetPage() ); + if ( !pPg.is() ) + return false; + for( short i = 0; i < m_nEntries; i++ ) + m_rStrm.GetIo().SetToPage( pPg, i, STG_FREE ); + nPage1++; + } + } + return true; +} + +// Release a chain + +bool StgFAT::FreePages( sal_Int32 nStart, bool bAll ) +{ + while( nStart >= 0 ) + { + rtl::Reference< StgPage > pPg = GetPhysPage( nStart << 2 ); + if( !pPg.is() ) + return false; + nStart = StgCache::GetFromPage( pPg, m_nOffset >> 2 ); + // The first released page is either set to EOF or FREE + m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, bAll ? STG_FREE : STG_EOF ); + bAll = true; + } + return true; +} + +///////////////////////////// class StgStrm + +// The base stream class provides basic functionality for seeking +// and accessing the data on a physical basis. It uses the built-in +// FAT class for the page allocations. + +StgStrm::StgStrm( StgIo& r ) + : m_nPos(0), + m_bBytePosValid(true), + m_rIo(r), + m_pEntry(nullptr), + m_nStart(STG_EOF), + m_nSize(0), + m_nPage(STG_EOF), + m_nOffset(0), + m_nPageSize(m_rIo.GetPhysPageSize()) +{ +} + +StgStrm::~StgStrm() +{ +} + +// Attach the stream to the given entry. + +void StgStrm::SetEntry( StgDirEntry& r ) +{ + r.m_aEntry.SetLeaf( STG_DATA, m_nStart ); + r.m_aEntry.SetSize( m_nSize ); + m_pEntry = &r; + r.SetDirty(); +} + +/* + * The page chain, is basically a singly linked list of slots each + * point to the next page. Instead of traversing the file structure + * for this each time build a simple flat in-memory vector list + * of pages. + */ +sal_Int32 StgStrm::scanBuildPageChainCache() +{ + if (m_nSize > 0) + { + m_aPagesCache.reserve(m_nSize/m_nPageSize); + m_aUsedPageNumbers.reserve(m_nSize/m_nPageSize); + } + + bool bError = false; + sal_Int32 nBgn = m_nStart; + sal_Int32 nOptSize = 0; + + // Track already scanned PageNumbers here and use them to + // see if an already counted page is re-visited + while( nBgn >= 0 && !bError ) + { + m_aPagesCache.push_back(nBgn); + nBgn = m_pFat->GetNextPage( nBgn ); + + //returned second is false if it already exists + if (!m_aUsedPageNumbers.insert(nBgn).second) + { + SAL_WARN ("sot", "Error: page number " << nBgn << " already in chain for stream"); + bError = true; + } + + nOptSize += m_nPageSize; + } + if (bError) + { + SAL_WARN("sot", "returning wrong format error"); + m_rIo.SetError( ERRCODE_IO_WRONGFORMAT ); + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + } + return nOptSize; +} + +// Compute page number and offset for the given byte position. +// If the position is behind the size, set the stream right +// behind the EOF. +bool StgStrm::Pos2Page( sal_Int32 nBytePos ) +{ + if ( !m_pFat ) + return false; + + // Values < 0 seek to the end + if( nBytePos < 0 || nBytePos >= m_nSize ) + nBytePos = m_nSize; + // Adjust the position back to offset 0 + m_nPos -= m_nOffset; + sal_Int32 nMask = ~( m_nPageSize - 1 ); + sal_Int32 nOld = m_nPos & nMask; + sal_Int32 nNew = nBytePos & nMask; + m_nOffset = static_cast<short>( nBytePos & ~nMask ); + m_nPos = nBytePos; + if (nOld == nNew) + return m_bBytePosValid; + + // See fdo#47644 for a .doc with a vast amount of pages where seeking around the + // document takes a colossal amount of time + + // Please Note: we build the pagescache incrementally as we go if necessary, + // so that a corrupted FAT doesn't poison the stream state for earlier reads + size_t nIdx = nNew / m_nPageSize; + if( nIdx >= m_aPagesCache.size() ) + { + // Extend the FAT cache ! ... + size_t nToAdd = nIdx + 1; + + if (m_aPagesCache.empty()) + { + m_aPagesCache.push_back( m_nStart ); + assert(m_aUsedPageNumbers.empty()); + m_aUsedPageNumbers.insert(m_nStart); + } + + nToAdd -= m_aPagesCache.size(); + + sal_Int32 nBgn = m_aPagesCache.back(); + + // Start adding pages while we can + while (nToAdd > 0 && nBgn >= 0) + { + sal_Int32 nOldBgn = nBgn; + nBgn = m_pFat->GetNextPage(nOldBgn); + if( nBgn >= 0 ) + { + //returned second is false if it already exists + if (!m_aUsedPageNumbers.insert(nBgn).second) + { + SAL_WARN ("sot", "Error: page number " << nBgn << " already in chain for stream"); + break; + } + + //very much the normal case + m_aPagesCache.push_back(nBgn); + --nToAdd; + } + } + } + + if ( nIdx > m_aPagesCache.size() ) + { + SAL_WARN("sot", "seek to index " << nIdx << + " beyond page cache size " << m_aPagesCache.size()); + // fdo#84229 - handle seek to end and back as eg. XclImpStream expects + m_nPage = STG_EOF; + m_nOffset = 0; + // Intriguingly in the past we didn't reset nPos to match the real + // length of the stream thus: + // nIdx = m_aPagesCache.size(); + // nPos = nPageSize * nIdx; + // so retain this behavior for now. + m_bBytePosValid = false; + return false; + } + + // special case: seek to 1st byte of new, unallocated page + // (in case the file size is a multiple of the page size) + if( nBytePos == m_nSize && !m_nOffset && nIdx > 0 && nIdx == m_aPagesCache.size() ) + { + nIdx--; + m_nOffset = m_nPageSize; + } + else if ( nIdx == m_aPagesCache.size() ) + { + m_nPage = STG_EOF; + m_bBytePosValid = false; + return false; + } + + m_nPage = m_aPagesCache[ nIdx ]; + + m_bBytePosValid = m_nPage >= 0; + return m_bBytePosValid; +} + +// Copy an entire stream. Both streams are allocated in the FAT. +// The target stream is this stream. + +bool StgStrm::Copy( sal_Int32 nFrom, sal_Int32 nBytes ) +{ + if ( !m_pFat ) + return false; + + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + + sal_Int32 nTo = m_nStart; + sal_Int32 nPgs = ( nBytes + m_nPageSize - 1 ) / m_nPageSize; + while( nPgs-- ) + { + if( nTo < 0 ) + { + m_rIo.SetError( SVSTREAM_FILEFORMAT_ERROR ); + return false; + } + m_rIo.Copy( nTo, nFrom ); + if( nFrom >= 0 ) + { + nFrom = m_pFat->GetNextPage( nFrom ); + if( nFrom < 0 ) + { + m_rIo.SetError( SVSTREAM_FILEFORMAT_ERROR ); + return false; + } + } + nTo = m_pFat->GetNextPage( nTo ); + } + return true; +} + +bool StgStrm::SetSize( sal_Int32 nBytes ) +{ + if ( nBytes < 0 || !m_pFat ) + return false; + + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + + // round up to page size + sal_Int32 nOld = ( ( m_nSize + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize; + sal_Int32 nNew = ( ( nBytes + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize; + if( nNew > nOld ) + { + if( !Pos2Page( m_nSize ) ) + return false; + sal_Int32 nBgn = m_pFat->AllocPages( m_nPage, ( nNew - nOld ) / m_nPageSize ); + if( nBgn == STG_EOF ) + return false; + if( m_nStart == STG_EOF ) + m_nStart = m_nPage = nBgn; + } + else if( nNew < nOld ) + { + bool bAll = ( nBytes == 0 ); + if( !Pos2Page( nBytes ) || !m_pFat->FreePages( m_nPage, bAll ) ) + return false; + if( bAll ) + m_nStart = m_nPage = STG_EOF; + } + if( m_pEntry ) + { + // change the dir entry? + if( !m_nSize || !nBytes ) + m_pEntry->m_aEntry.SetLeaf( STG_DATA, m_nStart ); + m_pEntry->m_aEntry.SetSize( nBytes ); + m_pEntry->SetDirty(); + } + m_nSize = nBytes; + m_pFat->SetLimit( GetPages() ); + return true; +} + +// Return the # of allocated pages + + +//////////////////////////// class StgFATStrm + +// The FAT stream class provides physical access to the master FAT. +// Since this access is implemented as a StgStrm, we can use the +// FAT allocator. + +StgFATStrm::StgFATStrm(StgIo& r, sal_Int32 nFatStrmSize) : StgStrm( r ) +{ + m_pFat.reset( new StgFAT( *this, true ) ); + m_nSize = nFatStrmSize; +} + +bool StgFATStrm::Pos2Page( sal_Int32 nBytePos ) +{ + // Values < 0 seek to the end + if( nBytePos < 0 || nBytePos >= m_nSize ) + nBytePos = m_nSize ? m_nSize - 1 : 0; + m_nPage = nBytePos / m_nPageSize; + m_nOffset = static_cast<short>( nBytePos % m_nPageSize ); + m_nPage = GetPage(m_nPage, false); + bool bValid = m_nPage >= 0; + SetPos(nBytePos, bValid); + return bValid; +} + +// Get the page number entry for the given page offset. + +sal_Int32 StgFATStrm::GetPage(sal_Int32 nOff, bool bMake, sal_uInt16 *pnMasterAlloc) +{ + OSL_ENSURE( nOff >= 0, "The offset may not be negative!" ); + if( pnMasterAlloc ) *pnMasterAlloc = 0; + if( nOff < StgHeader::GetFAT1Size() ) + return m_rIo.m_aHdr.GetFATPage( nOff ); + sal_Int32 nMaxPage = m_nSize >> 2; + nOff = nOff - StgHeader::GetFAT1Size(); + // number of master pages that we need to iterate through + sal_uInt16 nMasterCount = ( m_nPageSize >> 2 ) - 1; + sal_uInt16 nBlocks = nOff / nMasterCount; + // offset in the last master page + nOff = nOff % nMasterCount; + + rtl::Reference< StgPage > pOldPage; + rtl::Reference< StgPage > pMaster; + sal_Int32 nFAT = m_rIo.m_aHdr.GetFATChain(); + for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ ) + { + if( nFAT == STG_EOF || nFAT == STG_FREE ) + { + if( bMake ) + { + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + + // create a new master page + nFAT = nMaxPage++; + pMaster = m_rIo.Copy( nFAT ); + if ( pMaster.is() ) + { + for( short k = 0; k < static_cast<short>( m_nPageSize >> 2 ); k++ ) + m_rIo.SetToPage( pMaster, k, STG_FREE ); + // chaining + if( !pOldPage.is() ) + m_rIo.m_aHdr.SetFATChain( nFAT ); + else + m_rIo.SetToPage( pOldPage, nMasterCount, nFAT ); + if( nMaxPage >= m_rIo.GetPhysPages() ) + if( !m_rIo.SetSize( nMaxPage ) ) + return STG_EOF; + // mark the page as used + // make space for Masterpage + if( !pnMasterAlloc ) // create space oneself + { + if( !Pos2Page( nFAT << 2 ) ) + return STG_EOF; + rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true ); + if( !pPg.is() ) + return STG_EOF; + m_rIo.SetToPage( pPg, m_nOffset >> 2, STG_MASTER ); + } + else + (*pnMasterAlloc)++; + m_rIo.m_aHdr.SetMasters( nCount + 1 ); + pOldPage = pMaster; + } + } + } + else + { + pMaster = m_rIo.Get( nFAT, true ); + if ( pMaster.is() ) + { + nFAT = StgCache::GetFromPage( pMaster, nMasterCount ); + pOldPage = pMaster; + } + } + } + if( pMaster.is() ) + return StgCache::GetFromPage( pMaster, nOff ); + m_rIo.SetError( SVSTREAM_GENERALERROR ); + return STG_EOF; +} + + +// Set the page number entry for the given page offset. + +bool StgFATStrm::SetPage( short nOff, sal_Int32 nNewPage ) +{ + OSL_ENSURE( nOff >= 0, "The offset may not be negative!" ); + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + + bool bRes = true; + if( nOff < StgHeader::GetFAT1Size() ) + m_rIo.m_aHdr.SetFATPage( nOff, nNewPage ); + else + { + nOff = nOff - StgHeader::GetFAT1Size(); + // number of master pages that we need to iterate through + sal_uInt16 nMasterCount = ( m_nPageSize >> 2 ) - 1; + sal_uInt16 nBlocks = nOff / nMasterCount; + // offset in the last master page + nOff = nOff % nMasterCount; + + rtl::Reference< StgPage > pMaster; + sal_Int32 nFAT = m_rIo.m_aHdr.GetFATChain(); + for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ ) + { + if( nFAT == STG_EOF || nFAT == STG_FREE ) + { + pMaster = nullptr; + break; + } + pMaster = m_rIo.Get( nFAT, true ); + if ( pMaster.is() ) + nFAT = StgCache::GetFromPage( pMaster, nMasterCount ); + } + if( pMaster.is() ) + m_rIo.SetToPage( pMaster, nOff, nNewPage ); + else + { + m_rIo.SetError( SVSTREAM_GENERALERROR ); + bRes = false; + } + } + + // lock the page against access + if( bRes ) + { + Pos2Page( nNewPage << 2 ); + rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true ); + if( pPg.is() ) + m_rIo.SetToPage( pPg, m_nOffset >> 2, STG_FAT ); + else + bRes = false; + } + return bRes; +} + +bool StgFATStrm::SetSize( sal_Int32 nBytes ) +{ + if ( nBytes < 0 ) + return false; + + m_aPagesCache.clear(); + m_aUsedPageNumbers.clear(); + + // Set the number of entries to a multiple of the page size + short nOld = static_cast<short>( ( m_nSize + ( m_nPageSize - 1 ) ) / m_nPageSize ); + short nNew = static_cast<short>( + ( nBytes + ( m_nPageSize - 1 ) ) / m_nPageSize ) ; + if( nNew < nOld ) + { + // release master pages + for( short i = nNew; i < nOld; i++ ) + SetPage( i, STG_FREE ); + } + else + { + while( nOld < nNew ) + { + // allocate master pages + // find a free master page slot + sal_Int32 nPg = 0; + sal_uInt16 nMasterAlloc = 0; + nPg = GetPage( nOld, true, &nMasterAlloc ); + if( nPg == STG_EOF ) + return false; + // 4 Bytes have been used for Allocation of each MegaMasterPage + nBytes += nMasterAlloc << 2; + + // find a free page using the FAT allocator + sal_Int32 n = 1; + OSL_ENSURE( m_pFat, "The pointer is always initializer here!" ); + sal_Int32 nNewPage = m_pFat->FindBlock( n ); + if( nNewPage == STG_EOF ) + { + // no free pages found; create a new page + // Since all pages are allocated, extend + // the file size for the next page! + nNewPage = m_nSize >> 2; + // if a MegaMasterPage was created avoid taking + // the same Page + nNewPage += nMasterAlloc; + // adjust the file size if necessary + if( nNewPage >= m_rIo.GetPhysPages() ) + if( !m_rIo.SetSize( nNewPage + 1 ) ) + return false; + } + // Set up the page with empty entries + rtl::Reference< StgPage > pPg = m_rIo.Copy( nNewPage ); + if ( !pPg.is() ) + return false; + for( short j = 0; j < static_cast<short>( m_nPageSize >> 2 ); j++ ) + m_rIo.SetToPage( pPg, j, STG_FREE ); + + // store the page number into the master FAT + // Set the size before so the correct FAT can be found + m_nSize = ( nOld + 1 ) * m_nPageSize; + SetPage( nOld, nNewPage ); + + // MegaMasterPages were created, mark it them as used + + sal_uInt32 nMax = m_rIo.m_aHdr.GetMasters( ); + sal_uInt32 nFAT = m_rIo.m_aHdr.GetFATChain(); + if( nMasterAlloc ) + for( sal_uInt32 nCount = 0; nCount < nMax; nCount++ ) + { + if( !Pos2Page( nFAT << 2 ) ) + return false; + if( nMax - nCount <= nMasterAlloc ) + { + rtl::Reference< StgPage > piPg = m_rIo.Get( m_nPage, true ); + if( !piPg.is() ) + return false; + m_rIo.SetToPage( piPg, m_nOffset >> 2, STG_MASTER ); + } + rtl::Reference< StgPage > pPage = m_rIo.Get( nFAT, true ); + if( !pPage.is() ) return false; + nFAT = StgCache::GetFromPage( pPage, (m_nPageSize >> 2 ) - 1 ); + } + + nOld++; + // We have used up 4 bytes for the STG_FAT entry + nBytes += 4; + nNew = static_cast<short>( + ( nBytes + ( m_nPageSize - 1 ) ) / m_nPageSize ); + } + } + m_nSize = nNew * m_nPageSize; + m_rIo.m_aHdr.SetFATSize( nNew ); + return true; +} + +/////////////////////////// class StgDataStrm + +// This class is a normal physical stream which can be initialized +// either with an existing dir entry or an existing FAT chain. +// The stream has a size increment which normally is 1, but which can be +// set to any value is you want the size to be incremented by certain values. + +StgDataStrm::StgDataStrm( StgIo& r, sal_Int32 nBgn, sal_Int32 nLen ) : StgStrm( r ) +{ + Init( nBgn, nLen ); +} + +StgDataStrm::StgDataStrm( StgIo& r, StgDirEntry& p ) : StgStrm( r ) +{ + m_pEntry = &p; + Init( p.m_aEntry.GetLeaf( STG_DATA ), + p.m_aEntry.GetSize() ); +} + +void StgDataStrm::Init( sal_Int32 nBgn, sal_Int32 nLen ) +{ + if ( m_rIo.m_pFAT ) + m_pFat.reset( new StgFAT( *m_rIo.m_pFAT, true ) ); + + OSL_ENSURE( m_pFat, "The pointer should not be empty!" ); + + m_nStart = m_nPage = nBgn; + m_nSize = nLen; + m_nIncr = 1; + m_nOffset = 0; + if( nLen < 0 && m_pFat ) + { + // determine the actual size of the stream by scanning + // the FAT chain and counting the # of pages allocated + m_nSize = scanBuildPageChainCache(); + } +} + +// Set the size of a physical stream. + +bool StgDataStrm::SetSize( sal_Int32 nBytes ) +{ + if ( !m_pFat ) + return false; + + nBytes = ( ( nBytes + m_nIncr - 1 ) / m_nIncr ) * m_nIncr; + sal_Int32 nOldSz = m_nSize; + if( nOldSz != nBytes ) + { + if( !StgStrm::SetSize( nBytes ) ) + return false; + sal_Int32 nMaxPage = m_pFat->GetMaxPage(); + if( nMaxPage > m_rIo.GetPhysPages() ) + if( !m_rIo.SetSize( nMaxPage ) ) + return false; + // If we only allocated one page or less, create this + // page in the cache for faster throughput. The current + // position is the former EOF point. + if( ( m_nSize - 1 ) / m_nPageSize - ( nOldSz - 1 ) / m_nPageSize == 1 ) + { + Pos2Page( nBytes ); + if( m_nPage >= 0 ) + m_rIo.Copy( m_nPage ); + } + } + return true; +} + +// Get the address of the data byte at a specified offset. +// If bForce = true, a read of non-existent data causes +// a read fault. + +void* StgDataStrm::GetPtr( sal_Int32 Pos, bool bDirty ) +{ + if( Pos2Page( Pos ) ) + { + rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true/*bForce*/ ); + if (pPg.is() && m_nOffset < pPg->GetSize()) + { + if( bDirty ) + m_rIo.SetDirty( pPg ); + return static_cast<sal_uInt8 *>(pPg->GetData()) + m_nOffset; + } + } + return nullptr; +} + +// This could easily be adapted to a better algorithm by determining +// the amount of consecutable blocks before doing a read. The result +// is the number of bytes read. No error is generated on EOF. + +sal_Int32 StgDataStrm::Read( void* pBuf, sal_Int32 n ) +{ + if ( n < 0 ) + return 0; + + const auto nAvailable = m_nSize - GetPos(); + if (n > nAvailable) + n = nAvailable; + sal_Int32 nDone = 0; + while( n ) + { + short nBytes = m_nPageSize - m_nOffset; + rtl::Reference< StgPage > pPg; + if( static_cast<sal_Int32>(nBytes) > n ) + nBytes = static_cast<short>(n); + if( nBytes ) + { + short nRes; + void *p = static_cast<sal_uInt8 *>(pBuf) + nDone; + if( nBytes == m_nPageSize ) + { + pPg = m_rIo.Find( m_nPage ); + if( pPg.is() ) + { + // data is present, so use the cached data + memcpy( p, pPg->GetData(), nBytes ); + nRes = nBytes; + } + else + // do a direct (unbuffered) read + nRes = static_cast<short>(m_rIo.Read( m_nPage, p )) * m_nPageSize; + } + else + { + // partial block read through the cache. + pPg = m_rIo.Get( m_nPage, false ); + if( !pPg.is() ) + break; + memcpy( p, static_cast<sal_uInt8*>(pPg->GetData()) + m_nOffset, nBytes ); + nRes = nBytes; + } + nDone += nRes; + SetPos(GetPos() + nRes, true); + n -= nRes; + m_nOffset = m_nOffset + nRes; + if( nRes != nBytes ) + break; // read error or EOF + } + // Switch to next page if necessary + if (m_nOffset >= m_nPageSize && !Pos2Page(GetPos())) + break; + } + return nDone; +} + +sal_Int32 StgDataStrm::Write( const void* pBuf, sal_Int32 n ) +{ + if ( n < 0 ) + return 0; + + sal_Int32 nDone = 0; + if( ( GetPos() + n ) > m_nSize ) + { + sal_Int32 nOld = GetPos(); + if( !SetSize( nOld + n ) ) + return 0; + Pos2Page( nOld ); + } + while( n ) + { + short nBytes = m_nPageSize - m_nOffset; + rtl::Reference< StgPage > pPg; + if( static_cast<sal_Int32>(nBytes) > n ) + nBytes = static_cast<short>(n); + if( nBytes ) + { + short nRes; + const void *p = static_cast<const sal_uInt8 *>(pBuf) + nDone; + if( nBytes == m_nPageSize ) + { + pPg = m_rIo.Find( m_nPage ); + if( pPg.is() ) + { + // data is present, so use the cached data + memcpy( pPg->GetData(), p, nBytes ); + m_rIo.SetDirty( pPg ); + nRes = nBytes; + } + else + // do a direct (unbuffered) write + nRes = static_cast<short>(m_rIo.Write( m_nPage, p )) * m_nPageSize; + } + else + { + // partial block read through the cache. + pPg = m_rIo.Get( m_nPage, false ); + if( !pPg.is() ) + break; + memcpy( static_cast<sal_uInt8*>(pPg->GetData()) + m_nOffset, p, nBytes ); + m_rIo.SetDirty( pPg ); + nRes = nBytes; + } + nDone += nRes; + SetPos(GetPos() + nRes, true); + n -= nRes; + m_nOffset = m_nOffset + nRes; + if( nRes != nBytes ) + break; // read error + } + // Switch to next page if necessary + if( m_nOffset >= m_nPageSize && !Pos2Page(GetPos()) ) + break; + } + return nDone; +} + +//////////////////////////// class StgSmallStream + +// The small stream class provides access to streams with a size < 4096 bytes. +// This stream is a StgStream containing small pages. The FAT for this stream +// is also a StgStream. The start of the FAT is in the header at DataRootPage, +// the stream itself is pointed to by the root entry (it holds start & size). + +StgSmallStrm::StgSmallStrm( StgIo& r, sal_Int32 nBgn ) : StgStrm( r ) +{ + Init( nBgn, 0 ); +} + +StgSmallStrm::StgSmallStrm( StgIo& r, StgDirEntry& p ) : StgStrm( r ) +{ + m_pEntry = &p; + Init( p.m_aEntry.GetLeaf( STG_DATA ), + p.m_aEntry.GetSize() ); +} + +void StgSmallStrm::Init( sal_Int32 nBgn, sal_Int32 nLen ) +{ + if ( m_rIo.m_pDataFAT ) + m_pFat.reset( new StgFAT( *m_rIo.m_pDataFAT, false ) ); + m_pData = m_rIo.m_pDataStrm; + OSL_ENSURE( m_pFat && m_pData, "The pointers should not be empty!" ); + + m_nPageSize = m_rIo.GetDataPageSize(); + m_nStart = + m_nPage = nBgn; + m_nSize = nLen; +} + +// This could easily be adapted to a better algorithm by determining +// the amount of consecutable blocks before doing a read. The result +// is the number of bytes read. No error is generated on EOF. + +sal_Int32 StgSmallStrm::Read( void* pBuf, sal_Int32 n ) +{ + // We can safely assume that reads are not huge, since the + // small stream is likely to be < 64 KBytes. + sal_Int32 nBytePos = GetPos(); + if( ( nBytePos + n ) > m_nSize ) + n = m_nSize - nBytePos; + sal_Int32 nDone = 0; + while( n ) + { + short nBytes = m_nPageSize - m_nOffset; + if( static_cast<sal_Int32>(nBytes) > n ) + nBytes = static_cast<short>(n); + if( nBytes ) + { + if (!m_pData) + break; + sal_Int32 nPos; + if (o3tl::checked_multiply<sal_Int32>(m_nPage, m_nPageSize, nPos)) + break; + if (!m_pData->Pos2Page(nPos + m_nOffset)) + break; + // all reading through the stream + short nRes = static_cast<short>(m_pData->Read( static_cast<sal_uInt8*>(pBuf) + nDone, nBytes )); + nDone += nRes; + SetPos(GetPos() + nRes, true); + n -= nRes; + m_nOffset = m_nOffset + nRes; + // read problem? + if( nRes != nBytes ) + break; + } + // Switch to next page if necessary + if (m_nOffset >= m_nPageSize && !Pos2Page(GetPos())) + break; + } + return nDone; +} + +sal_Int32 StgSmallStrm::Write( const void* pBuf, sal_Int32 n ) +{ + // you can safely assume that reads are not huge, since the + // small stream is likely to be < 64 KBytes. + sal_Int32 nDone = 0; + sal_Int32 nOldPos = GetPos(); + if( ( nOldPos + n ) > m_nSize ) + { + if (!SetSize(nOldPos + n)) + return 0; + Pos2Page(nOldPos); + } + while( n ) + { + short nBytes = m_nPageSize - m_nOffset; + if( static_cast<sal_Int32>(nBytes) > n ) + nBytes = static_cast<short>(n); + if( nBytes ) + { + // all writing goes through the stream + sal_Int32 nDataPos = m_nPage * m_nPageSize + m_nOffset; + if ( !m_pData + || ( m_pData->GetSize() < ( nDataPos + nBytes ) + && !m_pData->SetSize( nDataPos + nBytes ) ) ) + break; + if( !m_pData->Pos2Page( nDataPos ) ) + break; + short nRes = static_cast<short>(m_pData->Write( static_cast<sal_uInt8 const *>(pBuf) + nDone, nBytes )); + nDone += nRes; + SetPos(GetPos() + nRes, true); + n -= nRes; + m_nOffset = m_nOffset + nRes; + // write problem? + if( nRes != nBytes ) + break; + } + // Switch to next page if necessary + if( m_nOffset >= m_nPageSize && !Pos2Page(GetPos()) ) + break; + } + return nDone; +} + +/////////////////////////// class StgTmpStrm + +// The temporary stream uses a memory stream if < 32K, otherwise a +// temporary file. + +#define THRESHOLD 32768L + +StgTmpStrm::StgTmpStrm( sal_uInt64 nInitSize ) + : SvMemoryStream( nInitSize > THRESHOLD + ? 16 + : ( nInitSize ? nInitSize : 16 ), 4096 ) +{ + m_pStrm = nullptr; + // this calls FlushData, so all members should be set by this time + SetBufferSize( 0 ); + if( nInitSize > THRESHOLD ) + SetSize( nInitSize ); +} + +bool StgTmpStrm::Copy( StgTmpStrm& rSrc ) +{ + sal_uInt64 n = rSrc.GetSize(); + const sal_uInt64 nCur = rSrc.Tell(); + SetSize( n ); + if( GetError() == ERRCODE_NONE ) + { + std::unique_ptr<sal_uInt8[]> p(new sal_uInt8[ 4096 ]); + rSrc.Seek( 0 ); + Seek( 0 ); + while( n ) + { + const sal_uInt64 nn = std::min<sal_uInt64>(n, 4096); + if (rSrc.ReadBytes( p.get(), nn ) != nn) + break; + if (WriteBytes( p.get(), nn ) != nn) + break; + n -= nn; + } + p.reset(); + rSrc.Seek( nCur ); + Seek( nCur ); + return n == 0; + } + else + return false; +} + +StgTmpStrm::~StgTmpStrm() +{ + if( m_pStrm ) + { + m_pStrm->Close(); + osl::File::remove( m_aName ); + delete m_pStrm; + } +} + +sal_uInt64 StgTmpStrm::GetSize() const +{ + sal_uInt64 n; + if( m_pStrm ) + { + n = m_pStrm->TellEnd(); + } + else + n = nEndOfData; + return n; +} + +void StgTmpStrm::SetSize(sal_uInt64 n) +{ + if( m_pStrm ) + m_pStrm->SetStreamSize( n ); + else + { + if( n > THRESHOLD ) + { + m_aName = utl::TempFile(nullptr, false).GetURL(); + std::unique_ptr<SvFileStream> s(new SvFileStream( m_aName, StreamMode::READWRITE )); + const sal_uInt64 nCur = Tell(); + sal_uInt64 i = nEndOfData; + std::unique_ptr<sal_uInt8[]> p(new sal_uInt8[ 4096 ]); + if( i ) + { + Seek( 0 ); + while( i ) + { + const sal_uInt64 nb = std::min<sal_uInt64>(i, 4096); + if (ReadBytes(p.get(), nb) == nb + && s->WriteBytes(p.get(), nb) == nb) + i -= nb; + else + break; + } + } + if( !i && n > nEndOfData ) + { + // We have to write one byte at the end of the file + // if the file is bigger than the memstream to see + // if it fits on disk + s->Seek(nEndOfData); + memset(p.get(), 0x00, 4096); + i = n - nEndOfData; + while (i) + { + const sal_uInt64 nb = std::min<sal_uInt64>(i, 4096); + if (s->WriteBytes(p.get(), nb) == nb) + i -= nb; + else + break; // error + } + s->Flush(); + if( s->GetError() != ERRCODE_NONE ) + i = 1; + } + Seek( nCur ); + s->Seek( nCur ); + if( i ) + { + SetError( s->GetError() ); + return; + } + m_pStrm = s.release(); + // Shrink the memory to 16 bytes, which seems to be the minimum + ReAllocateMemory( - ( static_cast<tools::Long>(nEndOfData) - 16 ) ); + } + else + { + if( n > nEndOfData ) + { + SvMemoryStream::SetSize(n); + } + else + nEndOfData = n; + } + } +} + +std::size_t StgTmpStrm::GetData( void* pData, std::size_t n ) +{ + if( m_pStrm ) + { + n = m_pStrm->ReadBytes( pData, n ); + SetError( m_pStrm->GetError() ); + return n; + } + else + return SvMemoryStream::GetData( pData, n ); +} + +std::size_t StgTmpStrm::PutData( const void* pData, std::size_t n ) +{ + sal_uInt32 nCur = Tell(); + sal_uInt32 nNew = nCur + n; + if( nNew > THRESHOLD && !m_pStrm ) + { + SetSize( nNew ); + if( GetError() != ERRCODE_NONE ) + return 0; + } + if( m_pStrm ) + { + nNew = m_pStrm->WriteBytes( pData, n ); + SetError( m_pStrm->GetError() ); + } + else + nNew = SvMemoryStream::PutData( pData, n ); + return nNew; +} + +sal_uInt64 StgTmpStrm::SeekPos(sal_uInt64 n) +{ + // check if a truncated STREAM_SEEK_TO_END was passed + assert(n != SAL_MAX_UINT32); + if( n == STREAM_SEEK_TO_END ) + n = GetSize(); + if( n > THRESHOLD && !m_pStrm ) + { + SetSize( n ); + if( GetError() != ERRCODE_NONE ) + return Tell(); + else + return n; + } + else if( m_pStrm ) + { + n = m_pStrm->Seek( n ); + SetError( m_pStrm->GetError() ); + return n; + } + else + return SvMemoryStream::SeekPos( n ); +} + +void StgTmpStrm::FlushData() +{ + if( m_pStrm ) + { + m_pStrm->Flush(); + SetError( m_pStrm->GetError() ); + } + else + SvMemoryStream::FlushData(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/stgstrms.hxx b/sot/source/sdstor/stgstrms.hxx new file mode 100644 index 000000000..51c08faf5 --- /dev/null +++ b/sot/source/sdstor/stgstrms.hxx @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SOT_SOURCE_SDSTOR_STGSTRMS_HXX +#define INCLUDED_SOT_SOURCE_SDSTOR_STGSTRMS_HXX + +#include <tools/stream.hxx> +#include <o3tl/sorted_vector.hxx> +#include <rtl/ref.hxx> +#include <vector> +#include <memory> + +class StgIo; +class StgStrm; +class StgPage; +class StgDirEntry; + +// The FAT class performs FAT operations on an underlying storage stream. +// This stream is either the physical FAT stream (bPhys == true ) or a normal +// storage stream, which then holds the FAT for small data allocations. + +class StgFAT +{ // FAT allocator + StgStrm& m_rStrm; // underlying stream + sal_Int32 m_nMaxPage; // highest page allocated so far + short m_nPageSize; // physical page size + short m_nEntries; // FAT entries per page + short m_nOffset; // current offset within page + sal_Int32 m_nLimit; // search limit recommendation + bool m_bPhys; // true: physical FAT + rtl::Reference< StgPage > GetPhysPage( sal_Int32 nPage ); + bool MakeChain( sal_Int32 nStart, sal_Int32 nPages ); + bool InitNew( sal_Int32 nPage1 ); +public: + StgFAT( StgStrm& rStrm, bool bMark ); + sal_Int32 FindBlock( sal_Int32& nPages ); + sal_Int32 GetNextPage( sal_Int32 nPg ); + sal_Int32 AllocPages( sal_Int32 nStart, sal_Int32 nPages ); + bool FreePages( sal_Int32 nStart, bool bAll ); + sal_Int32 GetMaxPage() const { return m_nMaxPage; } + void SetLimit( sal_Int32 n ) { m_nLimit = n; } +}; + +// The base stream class provides basic functionality for seeking +// and accessing the data on a physical basis. It uses the built-in +// FAT class for the page allocations. + +class StgStrm { // base class for all streams +private: + sal_Int32 m_nPos; // current byte position + bool m_bBytePosValid; // what Pos2Page returns for m_nPos +protected: + StgIo& m_rIo; // I/O system + std::unique_ptr<StgFAT> m_pFat; // FAT stream for allocations + StgDirEntry* m_pEntry; // dir entry (for ownership) + sal_Int32 m_nStart; // 1st data page + sal_Int32 m_nSize; // stream size in bytes + sal_Int32 m_nPage; // current logical page + short m_nOffset; // offset into current page + short m_nPageSize; // logical page size + std::vector<sal_Int32> m_aPagesCache; + o3tl::sorted_vector<sal_Int32> m_aUsedPageNumbers; + sal_Int32 scanBuildPageChainCache(); + bool Copy( sal_Int32 nFrom, sal_Int32 nBytes ); + void SetPos(sal_Int32 nPos, bool bValid) { m_nPos = nPos; m_bBytePosValid = bValid; } + explicit StgStrm( StgIo& ); +public: + virtual ~StgStrm(); + StgIo& GetIo() { return m_rIo; } + sal_Int32 GetPos() const { return m_nPos; } + sal_Int32 GetStart() const { return m_nStart; } + sal_Int32 GetSize() const { return m_nSize; } + sal_Int32 GetPage() const { return m_nPage; } + sal_Int32 GetPages() const { return ( m_nSize + m_nPageSize - 1 ) / m_nPageSize;} + short GetOffset() const { return m_nOffset;} + void SetEntry( StgDirEntry& ); + virtual bool SetSize( sal_Int32 ); + virtual bool Pos2Page( sal_Int32 nBytePos ); + virtual sal_Int32 Read( void*, sal_Int32 ) { return 0; } + virtual sal_Int32 Write( const void*, sal_Int32 ) { return 0; } + virtual bool IsSmallStrm() const { return false; } +}; + +// The FAT stream class provides physical access to the master FAT. +// Since this access is implemented as a StgStrm, we can use the +// FAT allocator. + +class StgFATStrm : public StgStrm { // the master FAT stream + virtual bool Pos2Page( sal_Int32 nBytePos ) override; + bool SetPage( short, sal_Int32 ); +public: + explicit StgFATStrm(StgIo&, sal_Int32 nFatStrmSize); + using StgStrm::GetPage; + sal_Int32 GetPage(sal_Int32, bool, sal_uInt16 *pnMasterAlloc = nullptr); + virtual bool SetSize( sal_Int32 ) override; +}; + +// The stream has a size increment which normally is 1, but which can be +// set to any value is you want the size to be incremented by certain values. + +class StgDataStrm : public StgStrm // a physical data stream +{ + short m_nIncr; // size adjust increment + void Init( sal_Int32 nBgn, sal_Int32 nLen ); +public: + StgDataStrm( StgIo&, sal_Int32 nBgn, sal_Int32 nLen=-1 ); + StgDataStrm( StgIo&, StgDirEntry& ); + void* GetPtr( sal_Int32 nPos, bool bDirty ); + void SetIncrement( short n ) { m_nIncr = n ; } + virtual bool SetSize( sal_Int32 ) override; + virtual sal_Int32 Read( void*, sal_Int32 ) override; + virtual sal_Int32 Write( const void*, sal_Int32 ) override; +}; + +// The small stream class provides access to streams with a size < 4096 bytes. +// This stream is a StgStream containing small pages. The FAT for this stream +// is also a StgStream. The start of the FAT is in the header at DataRootPage, +// the stream itself is pointed to by the root entry (it holds start & size). + +class StgSmallStrm : public StgStrm // a logical data stream +{ + StgStrm* m_pData; // the data stream + void Init( sal_Int32 nBgn, sal_Int32 nLen ); +public: + StgSmallStrm( StgIo&, sal_Int32 nBgn ); + StgSmallStrm( StgIo&, StgDirEntry& ); + virtual sal_Int32 Read( void*, sal_Int32 ) override; + virtual sal_Int32 Write( const void*, sal_Int32 ) override; + virtual bool IsSmallStrm() const override { return true; } +}; + +class StgTmpStrm : public SvMemoryStream +{ + OUString m_aName; + SvFileStream* m_pStrm; + using SvMemoryStream::GetData; + virtual std::size_t GetData( void* pData, std::size_t nSize ) override; + virtual std::size_t PutData( const void* pData, std::size_t nSize ) override; + virtual sal_uInt64 SeekPos( sal_uInt64 nPos ) override; + virtual void FlushData() override; + +public: + explicit StgTmpStrm( sal_uInt64=16 ); + virtual ~StgTmpStrm() override; + bool Copy( StgTmpStrm& ); + virtual void SetSize( sal_uInt64 ) override; + sal_uInt64 GetSize() const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/storage.cxx b/sot/source/sdstor/storage.cxx new file mode 100644 index 000000000..f1ff0455c --- /dev/null +++ b/sot/source/sdstor/storage.cxx @@ -0,0 +1,803 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <osl/file.hxx> +#include <sot/stg.hxx> +#include <sot/storinfo.hxx> +#include <sot/storage.hxx> +#include <sot/formats.hxx> +#include <sot/exchange.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <unotools/ucbhelper.hxx> +#include <comphelper/fileformat.h> +#include <com/sun/star/uno/Reference.h> + +#include <memory> + +using namespace ::com::sun::star; + +static SvLockBytesRef MakeLockBytes_Impl( const OUString & rName, StreamMode nMode ) +{ + SvLockBytesRef xLB; + if( !rName.isEmpty() ) + { + SvStream * pFileStm = new SvFileStream( rName, nMode ); + xLB = new SvLockBytes( pFileStm, true ); + } + else + { + SvStream * pCacheStm = new SvMemoryStream(); + xLB = new SvLockBytes( pCacheStm, true ); + } + return xLB; +} + +SotTempStream::SotTempStream( const OUString & rName, StreamMode nMode ) + : SvStream( MakeLockBytes_Impl( rName, nMode ).get() ) +{ + if( nMode & StreamMode::WRITE ) + m_isWritable = true; + else + m_isWritable = false; +} + +SotTempStream::~SotTempStream() +{ + FlushBuffer(); +} + +void SotTempStream::CopyTo( SotTempStream * pDestStm ) +{ + FlushBuffer(); // write all data + + sal_uInt64 nPos = Tell(); // save position + Seek( 0 ); + pDestStm->SetSize( 0 ); // empty target stream + + constexpr int BUFSIZE = 64 * 1024; + std::unique_ptr<sal_uInt8[]> pMem(new sal_uInt8[ BUFSIZE ]); + sal_Int32 nRead; + while (0 != (nRead = ReadBytes(pMem.get(), BUFSIZE))) + { + if (nRead != static_cast<sal_Int32>(pDestStm->WriteBytes(pMem.get(), nRead))) + { + SetError( SVSTREAM_GENERALERROR ); + break; + } + } + pMem.reset(); + + // set position + pDestStm->Seek( nPos ); + Seek( nPos ); +} + +SotStorageStream::SotStorageStream( BaseStorageStream * pStm ) + : pOwnStm(pStm) +{ + assert( pStm ); + if( StreamMode::WRITE & pStm->GetMode() ) + m_isWritable = true; + else + m_isWritable = false; + + SetError( pStm->GetError() ); + pStm->ResetError(); +} + +SotStorageStream::~SotStorageStream() +{ + Flush(); + delete pOwnStm; +} + +void SotStorageStream::ResetError() +{ + SvStream::ResetError(); + pOwnStm->ResetError(); +} + +std::size_t SotStorageStream::GetData(void* pData, std::size_t const nSize) +{ + std::size_t nRet = pOwnStm->Read( pData, nSize ); + SetError( pOwnStm->GetError() ); + return nRet; +} + +std::size_t SotStorageStream::PutData(const void* pData, std::size_t const nSize) +{ + std::size_t nRet = pOwnStm->Write( pData, nSize ); + SetError( pOwnStm->GetError() ); + return nRet; +} + +sal_uInt64 SotStorageStream::SeekPos(sal_uInt64 nPos) +{ + sal_uInt64 nRet = pOwnStm->Seek( nPos ); + SetError( pOwnStm->GetError() ); + return nRet; +} + +void SotStorageStream::FlushData() +{ + pOwnStm->Flush(); + SetError( pOwnStm->GetError() ); +} + +void SotStorageStream::SetSize(sal_uInt64 const nNewSize) +{ + sal_uInt64 const nPos = Tell(); + pOwnStm->SetSize( nNewSize ); + SetError( pOwnStm->GetError() ); + + if( nNewSize < nPos ) + // jump to the end + Seek( nNewSize ); +} + +sal_uInt32 SotStorageStream::GetSize() const +{ + sal_uInt64 nSize = const_cast<SotStorageStream*>(this)->TellEnd(); + return nSize; +} + +sal_uInt64 SotStorageStream::TellEnd() +{ + // Need to flush the buffer so we materialise the stream and return the correct answer + // otherwise we return a 0 value from StgEntry::GetSize + FlushBuffer(); + + return pOwnStm->GetSize(); +} + +void SotStorageStream::Commit() +{ + pOwnStm->Flush(); + if( pOwnStm->GetError() == ERRCODE_NONE ) + pOwnStm->Commit(); + SetError( pOwnStm->GetError() ); +} + +bool SotStorageStream::SetProperty( const OUString& rName, const css::uno::Any& rValue ) +{ + UCBStorageStream* pStg = dynamic_cast<UCBStorageStream*>( pOwnStm ); + if ( pStg ) + { + return pStg->SetProperty( rName, rValue ); + } + else + { + OSL_FAIL("Not implemented!"); + return false; + } +} + +/** + * SotStorage::SotStorage() + * + * A I... object must be passed to SvObject, because otherwise itself will + * create and define an IUnknown, so that all other I... objects would be + * destroyed with delete (Owner() == true). + * But IStorage objects are only used and not implemented by ourselves, + * therefore we pretend the IStorage object was passed from the outside + * and it will be freed with Release(). + * The CreateStorage methods are needed to create an IStorage object before the + * call of SvObject (Own, !Own automatic). + * If CreateStorage has created an object, then the RefCounter was already + * incremented. + * The transfer is done in pStorageCTor and the variable is NULL, if it didn't + * work. + */ +#define INIT_SotStorage() \ + : m_pOwnStg( nullptr ) \ + , m_pStorStm( nullptr ) \ + , m_nError( ERRCODE_NONE ) \ + , m_bIsRoot( false ) \ + , m_bDelStm( false ) \ + , m_nVersion( SOFFICE_FILEFORMAT_CURRENT ) + +#define ERASEMASK ( StreamMode::TRUNC | StreamMode::WRITE | StreamMode::SHARE_DENYALL ) + +SotStorage::SotStorage( const OUString & rName, StreamMode nMode ) + INIT_SotStorage() +{ + m_aName = rName; // save name + CreateStorage( true, nMode ); + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; +} + +void SotStorage::CreateStorage( bool bForceUCBStorage, StreamMode nMode ) +{ + DBG_ASSERT( !m_pStorStm && !m_pOwnStg, "Use only in ctor!" ); + if( !m_aName.isEmpty() ) + { + // named storage + if( ( nMode & ERASEMASK ) == ERASEMASK ) + ::utl::UCBContentHelper::Kill( m_aName ); + + INetURLObject aObj( m_aName ); + if ( aObj.GetProtocol() == INetProtocol::NotValid ) + { + OUString aURL; + osl::FileBase::getFileURLFromSystemPath( m_aName, aURL ); + aObj.SetURL( aURL ); + m_aName = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + // check the stream + m_pStorStm = ::utl::UcbStreamHelper::CreateStream( m_aName, nMode ).release(); + if ( m_pStorStm && m_pStorStm->GetError() ) + { + delete m_pStorStm; + m_pStorStm = nullptr; + } + + if ( m_pStorStm ) + { + // try as UCBStorage, next try as OLEStorage + bool bIsUCBStorage = UCBStorage::IsStorageFile( m_pStorStm ); + if ( !bIsUCBStorage && bForceUCBStorage ) + // if UCBStorage has priority, it should not be used only if it is really an OLEStorage + bIsUCBStorage = !Storage::IsStorageFile( m_pStorStm ); + + if ( bIsUCBStorage ) + { + // UCBStorage always works directly on the UCB content, so discard the stream first + delete m_pStorStm; + m_pStorStm = nullptr; + m_pOwnStg = new UCBStorage( m_aName, nMode, true, true/*bIsRoot*/ ); + } + else + { + // OLEStorage can be opened with a stream + m_pOwnStg = new Storage( *m_pStorStm, true ); + m_bDelStm = true; + } + } + else if ( bForceUCBStorage ) + { + m_pOwnStg = new UCBStorage( m_aName, nMode, true, true/*bIsRoot*/ ); + SetError( ERRCODE_IO_NOTSUPPORTED ); + } + else + { + m_pOwnStg = new Storage( m_aName, nMode, true ); + SetError( ERRCODE_IO_NOTSUPPORTED ); + } + } + else + { + // temporary storage + if ( bForceUCBStorage ) + m_pOwnStg = new UCBStorage( m_aName, nMode, true, true/*bIsRoot*/ ); + else + m_pOwnStg = new Storage( m_aName, nMode, true ); + m_aName = m_pOwnStg->GetName(); + } + + SetError( m_pOwnStg->GetError() ); + + SignAsRoot( m_pOwnStg->IsRoot() ); +} + +SotStorage::SotStorage( bool bUCBStorage, const OUString & rName, StreamMode nMode ) + INIT_SotStorage() +{ + m_aName = rName; + CreateStorage( bUCBStorage, nMode ); + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; +} + +SotStorage::SotStorage( BaseStorage * pStor ) + INIT_SotStorage() +{ + if ( pStor ) + { + m_aName = pStor->GetName(); // save name + SignAsRoot( pStor->IsRoot() ); + SetError( pStor->GetError() ); + } + + m_pOwnStg = pStor; + const ErrCode nErr = m_pOwnStg ? m_pOwnStg->GetError() : SVSTREAM_CANNOT_MAKE; + SetError( nErr ); + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; +} + +SotStorage::SotStorage( bool bUCBStorage, SvStream & rStm ) + INIT_SotStorage() +{ + SetError( rStm.GetError() ); + + // try as UCBStorage, next try as OLEStorage + if ( UCBStorage::IsStorageFile( &rStm ) || bUCBStorage ) + m_pOwnStg = new UCBStorage( rStm, false ); + else + m_pOwnStg = new Storage( rStm, false ); + + SetError( m_pOwnStg->GetError() ); + + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; + + SignAsRoot( m_pOwnStg->IsRoot() ); +} + +SotStorage::SotStorage( SvStream & rStm ) + INIT_SotStorage() +{ + SetError( rStm.GetError() ); + + // try as UCBStorage, next try as OLEStorage + if ( UCBStorage::IsStorageFile( &rStm ) ) + m_pOwnStg = new UCBStorage( rStm, false ); + else + m_pOwnStg = new Storage( rStm, false ); + + SetError( m_pOwnStg->GetError() ); + + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; + + SignAsRoot( m_pOwnStg->IsRoot() ); +} + +SotStorage::SotStorage( SvStream * pStm, bool bDelete ) + INIT_SotStorage() +{ + SetError( pStm->GetError() ); + + // try as UCBStorage, next try as OLEStorage + if ( UCBStorage::IsStorageFile( pStm ) ) + m_pOwnStg = new UCBStorage( *pStm, false ); + else + m_pOwnStg = new Storage( *pStm, false ); + + SetError( m_pOwnStg->GetError() ); + + m_pStorStm = pStm; + m_bDelStm = bDelete; + if ( IsOLEStorage() ) + m_nVersion = SOFFICE_FILEFORMAT_50; + + SignAsRoot( m_pOwnStg->IsRoot() ); +} + +SotStorage::~SotStorage() +{ + delete m_pOwnStg; + if( m_bDelStm ) + delete m_pStorStm; +} + +std::unique_ptr<SvMemoryStream> SotStorage::CreateMemoryStream() +{ + std::unique_ptr<SvMemoryStream> pStm(new SvMemoryStream( 0x8000, 0x8000 )); + tools::SvRef<SotStorage> aStg = new SotStorage( *pStm ); + if( CopyTo( aStg.get() ) ) + { + aStg->Commit(); + } + else + { + aStg.clear(); // release storage beforehand + pStm.reset(); + } + return pStm; +} + +bool SotStorage::IsStorageFile( const OUString & rFileName ) +{ + OUString aName( rFileName ); + INetURLObject aObj( aName ); + if ( aObj.GetProtocol() == INetProtocol::NotValid ) + { + OUString aURL; + osl::FileBase::getFileURLFromSystemPath( aName, aURL ); + aObj.SetURL( aURL ); + aName = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + std::unique_ptr<SvStream> pStm(::utl::UcbStreamHelper::CreateStream( aName, StreamMode::STD_READ )); + bool bRet = SotStorage::IsStorageFile( pStm.get() ); + return bRet; +} + +bool SotStorage::IsStorageFile( SvStream* pStream ) +{ + /** code for new storages must come first! **/ + if ( pStream ) + { + sal_uInt64 nPos = pStream->Tell(); + bool bRet = UCBStorage::IsStorageFile( pStream ); + if ( !bRet ) + bRet = Storage::IsStorageFile( pStream ); + pStream->Seek( nPos ); + return bRet; + } + else + return false; +} + +const OUString & SotStorage::GetName() const +{ + if( m_aName.isEmpty() && m_pOwnStg ) + const_cast<SotStorage *>(this)->m_aName = m_pOwnStg->GetName(); + return m_aName; +} + +void SotStorage::SetClass( const SvGlobalName & rName, + SotClipboardFormatId nOriginalClipFormat, + const OUString & rUserTypeName ) +{ + if( m_pOwnStg ) + m_pOwnStg->SetClass( rName, nOriginalClipFormat, rUserTypeName ); + else + SetError( SVSTREAM_GENERALERROR ); +} + +SvGlobalName SotStorage::GetClassName() +{ + SvGlobalName aGN; + if( m_pOwnStg ) + aGN = m_pOwnStg->GetClassName(); + else + SetError( SVSTREAM_GENERALERROR ); + return aGN; +} + +SotClipboardFormatId SotStorage::GetFormat() +{ + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + if( m_pOwnStg ) + nFormat = m_pOwnStg->GetFormat(); + else + SetError( SVSTREAM_GENERALERROR ); + return nFormat; +} + +OUString SotStorage::GetUserName() +{ + OUString aName; + if( m_pOwnStg ) + aName = m_pOwnStg->GetUserName(); + else + SetError( SVSTREAM_GENERALERROR ); + return aName; +} + +void SotStorage::FillInfoList( SvStorageInfoList * pFillList ) const +{ + if( m_pOwnStg ) + m_pOwnStg->FillInfoList( pFillList ); +} + +bool SotStorage::CopyTo( SotStorage * pDestStg ) +{ + if( m_pOwnStg && pDestStg->m_pOwnStg ) + { + m_pOwnStg->CopyTo( pDestStg->m_pOwnStg ); + SetError( m_pOwnStg->GetError() ); + pDestStg->m_aKey = m_aKey; + pDestStg->m_nVersion = m_nVersion; + } + else + SetError( SVSTREAM_GENERALERROR ); + + return ERRCODE_NONE == GetError(); +} + +bool SotStorage::Commit() +{ + if( m_pOwnStg ) + { + if( !m_pOwnStg->Commit() ) + SetError( m_pOwnStg->GetError() ); + } + else + SetError( SVSTREAM_GENERALERROR ); + + return ERRCODE_NONE == GetError(); +} + +tools::SvRef<SotStorageStream> SotStorage::OpenSotStream( const OUString & rEleName, + StreamMode nMode ) +{ + tools::SvRef<SotStorageStream> pStm; + if( m_pOwnStg ) + { + // enable full Ole patches, + // regardless what is coming, only exclusively allowed + nMode |= StreamMode::SHARE_DENYALL; + ErrCode nE = m_pOwnStg->GetError(); + BaseStorageStream * p = m_pOwnStg->OpenStream( rEleName, nMode ); + pStm = new SotStorageStream( p ); + + if( !nE ) + m_pOwnStg->ResetError(); // don't set error + if( nMode & StreamMode::TRUNC ) + pStm->SetSize( 0 ); + } + else + SetError( SVSTREAM_GENERALERROR ); + + return pStm; +} + +SotStorage * SotStorage::OpenSotStorage( const OUString & rEleName, + StreamMode nMode, + bool transacted ) +{ + if( m_pOwnStg ) + { + nMode |= StreamMode::SHARE_DENYALL; + ErrCode nE = m_pOwnStg->GetError(); + BaseStorage * p = m_pOwnStg->OpenStorage(rEleName, nMode, !transacted); + if( p ) + { + SotStorage * pStor = new SotStorage( p ); + if( !nE ) + m_pOwnStg->ResetError(); // don't set error + + return pStor; + } + } + + SetError( SVSTREAM_GENERALERROR ); + + return nullptr; +} + +bool SotStorage::IsStorage( const OUString & rEleName ) const +{ + // a little bit faster + if( m_pOwnStg ) + return m_pOwnStg->IsStorage( rEleName ); + + return false; +} + +bool SotStorage::IsStream( const OUString & rEleName ) const +{ + // a little bit faster + if( m_pOwnStg ) + return m_pOwnStg->IsStream( rEleName ); + + return false; +} + +bool SotStorage::IsContained( const OUString & rEleName ) const +{ + // a little bit faster + if( m_pOwnStg ) + return m_pOwnStg->IsContained( rEleName ); + + return false; +} + +bool SotStorage::Remove( const OUString & rEleName ) +{ + if( m_pOwnStg ) + { + m_pOwnStg->Remove( rEleName ); + SetError( m_pOwnStg->GetError() ); + } + else + SetError( SVSTREAM_GENERALERROR ); + + return ERRCODE_NONE == GetError(); +} + +bool SotStorage::CopyTo( const OUString & rEleName, + SotStorage * pNewSt, const OUString & rNewName ) +{ + if( m_pOwnStg ) + { + m_pOwnStg->CopyTo( rEleName, pNewSt->m_pOwnStg, rNewName ); + SetError( m_pOwnStg->GetError() ); + SetError( pNewSt->GetError() ); + } + else + SetError( SVSTREAM_GENERALERROR ); + + return ERRCODE_NONE == GetError(); +} + +bool SotStorage::Validate() +{ + DBG_ASSERT( m_bIsRoot, "Validate only if root storage" ); + if( m_pOwnStg ) + return m_pOwnStg->ValidateFAT(); + else + return true; +} + +bool SotStorage::IsOLEStorage() const +{ + UCBStorage* pStg = dynamic_cast<UCBStorage*>( m_pOwnStg ); + return !pStg; +} + +bool SotStorage::IsOLEStorage( const OUString & rFileName ) +{ + return Storage::IsStorageFile( rFileName ); +} + +bool SotStorage::IsOLEStorage( SvStream* pStream ) +{ + return Storage::IsStorageFile( pStream ); +} + +SotStorage* SotStorage::OpenOLEStorage( const css::uno::Reference < css::embed::XStorage >& xStorage, + const OUString& rEleName, StreamMode nMode ) +{ + sal_Int32 nEleMode = embed::ElementModes::SEEKABLEREAD; + if ( nMode & StreamMode::WRITE ) + nEleMode |= embed::ElementModes::WRITE; + if ( nMode & StreamMode::TRUNC ) + nEleMode |= embed::ElementModes::TRUNCATE; + if ( nMode & StreamMode::NOCREATE ) + nEleMode |= embed::ElementModes::NOCREATE; + + std::unique_ptr<SvStream> pStream; + try + { + uno::Reference < io::XStream > xStream = xStorage->openStreamElement( rEleName, nEleMode ); + + // TODO/LATER: should it be done this way? + if ( nMode & StreamMode::WRITE ) + { + uno::Reference < beans::XPropertySet > xStreamProps( xStream, uno::UNO_QUERY_THROW ); + xStreamProps->setPropertyValue( "MediaType", + uno::Any( OUString( "application/vnd.sun.star.oleobject" ) ) ); + } + + pStream = utl::UcbStreamHelper::CreateStream( xStream ); + } + catch ( uno::Exception& ) + { + //TODO/LATER: ErrorHandling + pStream.reset( new SvMemoryStream ); + pStream->SetError( ERRCODE_IO_GENERAL ); + } + + return new SotStorage( pStream.release(), true ); +} + +SotClipboardFormatId SotStorage::GetFormatID( const css::uno::Reference < css::embed::XStorage >& xStorage ) +{ + uno::Reference< beans::XPropertySet > xProps( xStorage, uno::UNO_QUERY ); + if ( !xProps.is() ) + return SotClipboardFormatId::NONE; + + OUString aMediaType; + try + { + xProps->getPropertyValue("MediaType") >>= aMediaType; + } + catch (uno::Exception const&) + { + TOOLS_INFO_EXCEPTION("sot", "SotStorage::GetFormatID"); + } + + if ( !aMediaType.isEmpty() ) + { + css::datatransfer::DataFlavor aDataFlavor; + aDataFlavor.MimeType = aMediaType; + return SotExchange::GetFormat( aDataFlavor ); + } + + return SotClipboardFormatId::NONE; +} + +sal_Int32 SotStorage::GetVersion( const css::uno::Reference < css::embed::XStorage >& xStorage ) +{ + SotClipboardFormatId nSotFormatID = SotStorage::GetFormatID( xStorage ); + switch( nSotFormatID ) + { + case SotClipboardFormatId::STARWRITER_8: + case SotClipboardFormatId::STARWRITER_8_TEMPLATE: + case SotClipboardFormatId::STARWRITERWEB_8: + case SotClipboardFormatId::STARWRITERGLOB_8: + case SotClipboardFormatId::STARWRITERGLOB_8_TEMPLATE: + case SotClipboardFormatId::STARDRAW_8: + case SotClipboardFormatId::STARDRAW_8_TEMPLATE: + case SotClipboardFormatId::STARIMPRESS_8: + case SotClipboardFormatId::STARIMPRESS_8_TEMPLATE: + case SotClipboardFormatId::STARCALC_8: + case SotClipboardFormatId::STARCALC_8_TEMPLATE: + case SotClipboardFormatId::STARCHART_8: + case SotClipboardFormatId::STARCHART_8_TEMPLATE: + case SotClipboardFormatId::STARMATH_8: + case SotClipboardFormatId::STARMATH_8_TEMPLATE: + return SOFFICE_FILEFORMAT_8; + case SotClipboardFormatId::STARWRITER_60: + case SotClipboardFormatId::STARWRITERWEB_60: + case SotClipboardFormatId::STARWRITERGLOB_60: + case SotClipboardFormatId::STARDRAW_60: + case SotClipboardFormatId::STARIMPRESS_60: + case SotClipboardFormatId::STARCALC_60: + case SotClipboardFormatId::STARCHART_60: + case SotClipboardFormatId::STARMATH_60: + return SOFFICE_FILEFORMAT_60; + default: break; + } + + return 0; +} + +namespace +{ + void traverse(const tools::SvRef<SotStorage>& rStorage, std::vector<unsigned char>& rBuf) + { + SvStorageInfoList infos; + + rStorage->FillInfoList(&infos); + + for (const auto& info: infos) + { + if (info.IsStream()) + { + // try to open and read all content + tools::SvRef<SotStorageStream> xStream(rStorage->OpenSotStream(info.GetName(), StreamMode::STD_READ)); + const size_t nSize = xStream->GetSize(); + const size_t nRead = xStream->ReadBytes(rBuf.data(), nSize); + SAL_INFO("sot", "Read " << nRead << "bytes"); + } + else if (info.IsStorage()) + { + tools::SvRef<SotStorage> xStorage(rStorage->OpenSotStorage(info.GetName(), StreamMode::STD_READ)); + + // continue with children + traverse(xStorage, rBuf); + } + } + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportOLE2(SvStream &rStream) +{ + try + { + size_t nSize = rStream.remainingSize(); + tools::SvRef<SotStorage> xRootStorage(new SotStorage(&rStream, false)); + std::vector<unsigned char> aTmpBuf(nSize); + traverse(xRootStorage, aTmpBuf); + } + catch (...) + { + return false; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/storinfo.cxx b/sot/source/sdstor/storinfo.cxx new file mode 100644 index 000000000..d8480e86a --- /dev/null +++ b/sot/source/sdstor/storinfo.cxx @@ -0,0 +1,98 @@ +/* -*- 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 <sot/storinfo.hxx> +#include <sot/exchange.hxx> +#include <tools/stream.hxx> +#include <vcl/errcode.hxx> +#include <memory> + +/************** class SvStorageInfo ************************************** +*************************************************************************/ +SotClipboardFormatId ReadClipboardFormat( SvStream & rStm ) +{ + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + sal_Int32 nLen = 0; + rStm.ReadInt32( nLen ); + if( rStm.eof() ) + rStm.SetError( SVSTREAM_GENERALERROR ); + if( nLen > 0 ) + { + // get a string name + std::unique_ptr<char[]> p(new( ::std::nothrow ) char[ nLen ]); + if (p && rStm.ReadBytes(p.get(), nLen) == static_cast<std::size_t>(nLen)) + { + nFormat = SotExchange::RegisterFormatName(OUString(p.get(), nLen-1, RTL_TEXTENCODING_ASCII_US)); + } + else + rStm.SetError( SVSTREAM_GENERALERROR ); + } + else if( nLen == -1 ) + { + // Windows clipboard format + // SV and Win match (up to and including SotClipboardFormatId::GDIMETAFILE) + sal_uInt32 nTmp; + rStm.ReadUInt32( nTmp ); + nFormat = static_cast<SotClipboardFormatId>(nTmp); + } + else if( nLen == -2 ) + { + sal_uInt32 nTmp; + rStm.ReadUInt32( nTmp ); + nFormat = static_cast<SotClipboardFormatId>(nTmp); + // Mac clipboard format + // ??? not implemented + rStm.SetError( SVSTREAM_GENERALERROR ); + } + else if( nLen != 0 ) + { + // unknown identifier + rStm.SetError( SVSTREAM_GENERALERROR ); + } + return nFormat; +} + +void WriteClipboardFormat( SvStream & rStm, SotClipboardFormatId nFormat ) +{ + // determine the clipboard format string + OUString aCbFmt; + if( nFormat > SotClipboardFormatId::GDIMETAFILE ) + aCbFmt = SotExchange::GetFormatName( nFormat ); + if( !aCbFmt.isEmpty() ) + { + OString aAsciiCbFmt(OUStringToOString(aCbFmt, + RTL_TEXTENCODING_ASCII_US)); + rStm.WriteInt32( aAsciiCbFmt.getLength() + 1 ); + rStm.WriteOString( aAsciiCbFmt ); + rStm.WriteUChar( 0 ); + } + else if( nFormat != SotClipboardFormatId::NONE ) + { + rStm.WriteInt32( -1 ) // for Windows + .WriteInt32( static_cast<sal_Int32>(nFormat) ); + } + else + { + rStm.WriteInt32( 0 ); // no clipboard format + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sot/source/sdstor/ucbstorage.cxx b/sot/source/sdstor/ucbstorage.cxx new file mode 100644 index 000000000..4e2dc1a2a --- /dev/null +++ b/sot/source/sdstor/ucbstorage.cxx @@ -0,0 +1,2845 @@ +/* -*- 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/io/NotConnectedException.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <ucbhelper/content.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/ucb/NameClash.hpp> +#include <unotools/tempfile.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/packages/manifest/ManifestWriter.hpp> +#include <com/sun/star/packages/manifest/ManifestReader.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> + +#include <memory> +#include <optional> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <tools/ref.hxx> +#include <tools/debug.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/ucbhelper.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/implbase.hxx> +#include <ucbhelper/commandenvironment.hxx> + +#include <sot/stg.hxx> +#include <sot/storinfo.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <comphelper/classids.hxx> + +#include <mutex> +#include <utility> +#include <vector> + +namespace com::sun::star::ucb { class XCommandEnvironment; } + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::sdbc; +using namespace ::ucbhelper; + +#if OSL_DEBUG_LEVEL > 0 +static int nOpenFiles=0; +static int nOpenStreams=0; +#endif + +typedef ::cppu::WeakImplHelper < XInputStream, XSeekable > FileInputStreamWrapper_Base; + +namespace { + +class FileStreamWrapper_Impl : public FileInputStreamWrapper_Base +{ +protected: + std::mutex m_aMutex; + OUString m_aURL; + std::unique_ptr<SvStream> m_pSvStream; + +public: + explicit FileStreamWrapper_Impl(OUString aName); + virtual ~FileStreamWrapper_Impl() override; + + virtual void SAL_CALL seek( sal_Int64 _nLocation ) override; + virtual sal_Int64 SAL_CALL getPosition( ) override; + virtual sal_Int64 SAL_CALL getLength( ) override; + virtual sal_Int32 SAL_CALL readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) override; + virtual sal_Int32 SAL_CALL readSomeBytes( 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; + +protected: + void checkConnected(); + void checkError(); +}; + +} + +FileStreamWrapper_Impl::FileStreamWrapper_Impl( OUString aName ) + : m_aURL(std::move( aName )) +{ + // if no URL is provided the stream is empty +} + + +FileStreamWrapper_Impl::~FileStreamWrapper_Impl() +{ + if ( m_pSvStream ) + { + m_pSvStream.reset(); +#if OSL_DEBUG_LEVEL > 0 + --nOpenFiles; +#endif + } + + if (!m_aURL.isEmpty()) + osl::File::remove(m_aURL); +} + + +sal_Int32 SAL_CALL FileStreamWrapper_Impl::readBytes(Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) +{ + if ( m_aURL.isEmpty() ) + { + aData.realloc( 0 ); + return 0; + } + + checkConnected(); + + if (nBytesToRead < 0) + throw BufferSizeExceededException(OUString(),static_cast<XWeak*>(this)); + + std::scoped_lock aGuard( m_aMutex ); + + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + sal_uInt32 nRead = m_pSvStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead); + checkError(); + + // if read characters < MaxLength, adjust sequence + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} + + +sal_Int32 SAL_CALL FileStreamWrapper_Impl::readSomeBytes(Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead) +{ + if ( m_aURL.isEmpty() ) + { + aData.realloc( 0 ); + return 0; + } + + checkError(); + + if (nMaxBytesToRead < 0) + throw BufferSizeExceededException(OUString(),static_cast<XWeak*>(this)); + + if (m_pSvStream->eof()) + { + aData.realloc(0); + return 0; + } + else + return readBytes(aData, nMaxBytesToRead); +} + + +void SAL_CALL FileStreamWrapper_Impl::skipBytes(sal_Int32 nBytesToSkip) +{ + if ( m_aURL.isEmpty() ) + return; + + std::scoped_lock aGuard( m_aMutex ); + checkError(); + + m_pSvStream->SeekRel(nBytesToSkip); + checkError(); +} + + +sal_Int32 SAL_CALL FileStreamWrapper_Impl::available() +{ + if ( m_aURL.isEmpty() ) + return 0; + + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + sal_Int64 nAvailable = m_pSvStream->remainingSize(); + checkError(); + + return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable); +} + + +void SAL_CALL FileStreamWrapper_Impl::closeInput() +{ + if ( m_aURL.isEmpty() ) + return; + + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + m_pSvStream.reset(); +#if OSL_DEBUG_LEVEL > 0 + --nOpenFiles; +#endif + osl::File::remove(m_aURL); + m_aURL.clear(); +} + + +void SAL_CALL FileStreamWrapper_Impl::seek( sal_Int64 _nLocation ) +{ + if ( m_aURL.isEmpty() ) + return; + + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + m_pSvStream->Seek(static_cast<sal_uInt32>(_nLocation)); + checkError(); +} + + +sal_Int64 SAL_CALL FileStreamWrapper_Impl::getPosition( ) +{ + if ( m_aURL.isEmpty() ) + return 0; + + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + sal_uInt32 nPos = m_pSvStream->Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} + + +sal_Int64 SAL_CALL FileStreamWrapper_Impl::getLength( ) +{ + if ( m_aURL.isEmpty() ) + return 0; + + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + checkError(); + + sal_Int64 nEndPos = m_pSvStream->TellEnd(); + + return nEndPos; +} + + +void FileStreamWrapper_Impl::checkConnected() +{ + if ( m_aURL.isEmpty() ) + throw NotConnectedException(OUString(), static_cast<XWeak*>(this)); + if ( !m_pSvStream ) + { + m_pSvStream = ::utl::UcbStreamHelper::CreateStream( m_aURL, StreamMode::STD_READ ); +#if OSL_DEBUG_LEVEL > 0 + ++nOpenFiles; +#endif + } +} + + +void FileStreamWrapper_Impl::checkError() +{ + checkConnected(); + + if (m_pSvStream->SvStream::GetError() != ERRCODE_NONE) + // TODO: really evaluate the error + throw NotConnectedException(OUString(), static_cast<XWeak*>(this)); +} + + +#define COMMIT_RESULT_FAILURE 0 +#define COMMIT_RESULT_NOTHING_TO_DO 1 +#define COMMIT_RESULT_SUCCESS 2 + +static SotClipboardFormatId GetFormatId_Impl( const SvGlobalName& aName ) +{ + if ( aName == SvGlobalName( SO3_SW_CLASSID_60 ) ) + return SotClipboardFormatId::STARWRITER_60; + if ( aName == SvGlobalName( SO3_SWWEB_CLASSID_60 ) ) + return SotClipboardFormatId::STARWRITERWEB_60; + if ( aName == SvGlobalName( SO3_SWGLOB_CLASSID_60 ) ) + return SotClipboardFormatId::STARWRITERGLOB_60; + if ( aName == SvGlobalName( SO3_SDRAW_CLASSID_60 ) ) + return SotClipboardFormatId::STARDRAW_60; + if ( aName == SvGlobalName( SO3_SIMPRESS_CLASSID_60 ) ) + return SotClipboardFormatId::STARIMPRESS_60; + if ( aName == SvGlobalName( SO3_SC_CLASSID_60 ) ) + return SotClipboardFormatId::STARCALC_60; + if ( aName == SvGlobalName( SO3_SCH_CLASSID_60 ) ) + return SotClipboardFormatId::STARCHART_60; + if ( aName == SvGlobalName( SO3_SM_CLASSID_60 ) ) + return SotClipboardFormatId::STARMATH_60; + if ( aName == SvGlobalName( SO3_OUT_CLASSID ) || + aName == SvGlobalName( SO3_APPLET_CLASSID ) || + aName == SvGlobalName( SO3_PLUGIN_CLASSID ) || + aName == SvGlobalName( SO3_IFRAME_CLASSID ) ) + // allowed, but not supported + return SotClipboardFormatId::NONE; + else + { + OSL_FAIL( "Unknown UCB storage format!" ); + return SotClipboardFormatId::NONE; + } +} + + +static SvGlobalName GetClassId_Impl( SotClipboardFormatId nFormat ) +{ + switch ( nFormat ) + { + case SotClipboardFormatId::STARWRITER_8 : + case SotClipboardFormatId::STARWRITER_8_TEMPLATE : + return SvGlobalName( SO3_SW_CLASSID_60 ); + case SotClipboardFormatId::STARWRITERWEB_8 : + return SvGlobalName( SO3_SWWEB_CLASSID_60 ); + case SotClipboardFormatId::STARWRITERGLOB_8 : + case SotClipboardFormatId::STARWRITERGLOB_8_TEMPLATE : + return SvGlobalName( SO3_SWGLOB_CLASSID_60 ); + case SotClipboardFormatId::STARDRAW_8 : + case SotClipboardFormatId::STARDRAW_8_TEMPLATE : + return SvGlobalName( SO3_SDRAW_CLASSID_60 ); + case SotClipboardFormatId::STARIMPRESS_8 : + case SotClipboardFormatId::STARIMPRESS_8_TEMPLATE : + return SvGlobalName( SO3_SIMPRESS_CLASSID_60 ); + case SotClipboardFormatId::STARCALC_8 : + case SotClipboardFormatId::STARCALC_8_TEMPLATE : + return SvGlobalName( SO3_SC_CLASSID_60 ); + case SotClipboardFormatId::STARCHART_8 : + case SotClipboardFormatId::STARCHART_8_TEMPLATE : + return SvGlobalName( SO3_SCH_CLASSID_60 ); + case SotClipboardFormatId::STARMATH_8 : + case SotClipboardFormatId::STARMATH_8_TEMPLATE : + return SvGlobalName( SO3_SM_CLASSID_60 ); + case SotClipboardFormatId::STARWRITER_60 : + return SvGlobalName( SO3_SW_CLASSID_60 ); + case SotClipboardFormatId::STARWRITERWEB_60 : + return SvGlobalName( SO3_SWWEB_CLASSID_60 ); + case SotClipboardFormatId::STARWRITERGLOB_60 : + return SvGlobalName( SO3_SWGLOB_CLASSID_60 ); + case SotClipboardFormatId::STARDRAW_60 : + return SvGlobalName( SO3_SDRAW_CLASSID_60 ); + case SotClipboardFormatId::STARIMPRESS_60 : + return SvGlobalName( SO3_SIMPRESS_CLASSID_60 ); + case SotClipboardFormatId::STARCALC_60 : + return SvGlobalName( SO3_SC_CLASSID_60 ); + case SotClipboardFormatId::STARCHART_60 : + return SvGlobalName( SO3_SCH_CLASSID_60 ); + case SotClipboardFormatId::STARMATH_60 : + return SvGlobalName( SO3_SM_CLASSID_60 ); + default : + return SvGlobalName(); + } +} + +// All storage and streams are refcounted internally; outside of this classes they are only accessible through a handle +// class, that uses the refcounted object as impl-class. + +class UCBStorageStream_Impl : public SvRefBase, public SvStream +{ + virtual ~UCBStorageStream_Impl() override; +public: + + virtual std::size_t GetData(void* pData, std::size_t nSize) override; + virtual std::size_t PutData(const void* pData, std::size_t nSize) override; + virtual sal_uInt64 SeekPos( sal_uInt64 nPos ) override; + virtual void SetSize( sal_uInt64 nSize ) override; + virtual void FlushData() override; + virtual void ResetError() override; + + UCBStorageStream* m_pAntiImpl; // only valid if an external reference exists + + OUString m_aOriginalName;// the original name before accessing the stream + OUString m_aName; // the actual name ( changed with a Rename command at the parent ) + OUString m_aURL; // the full path name to create the content + OUString m_aContentType; + OUString m_aOriginalContentType; + OString m_aKey; + ::ucbhelper::Content* m_pContent; // the content that provides the data + Reference<XInputStream> m_rSource; // the stream covering the original data of the content + std::unique_ptr<SvStream> m_pStream; // the stream worked on; for readonly streams it is the original stream of the content + // for read/write streams it's a copy into a temporary file + OUString m_aTempURL; // URL of this temporary stream + ErrCode m_nError; + StreamMode m_nMode; // open mode ( read/write/trunc/nocreate/sharing ) + bool m_bSourceRead; // Source still contains useful information + bool m_bModified; // only modified streams will be sent to the original content + bool m_bCommited; // sending the streams is coordinated by the root storage of the package + bool m_bDirect; // the storage and its streams are opened in direct mode; for UCBStorages + // this means that the root storage does an autocommit when its external + // reference is destroyed + bool m_bIsOLEStorage;// an OLEStorage on a UCBStorageStream makes this an Autocommit-stream + + UCBStorageStream_Impl( const OUString&, StreamMode, UCBStorageStream*, bool, + bool bRepair, Reference< XProgressHandler > const & xProgress ); + + void Free(); + bool Init(); + bool Clear(); + sal_Int16 Commit(); // if modified and committed: transfer an XInputStream to the content + void Revert(); // discard all changes + BaseStorage* CreateStorage();// create an OLE Storage on the UCBStorageStream + sal_uInt64 GetSize(); + + sal_uInt64 ReadSourceWriteTemporary( sal_uInt64 aLength ); // read aLength from source and copy to temporary, + // no seeking is produced + void ReadSourceWriteTemporary(); // read source till the end and copy to temporary, + + void CopySourceToTemporary(); // same as ReadSourceWriteToTemporary() + // but the writing is done at the end of temporary + // pointer position is not changed + using SvStream::SetError; + void SetError( ErrCode nError ); + void PrepareCachedForReopen( StreamMode nMode ); +}; + +typedef tools::SvRef<UCBStorageStream_Impl> UCBStorageStream_ImplRef; + +struct UCBStorageElement_Impl; +typedef std::vector<std::unique_ptr<UCBStorageElement_Impl>> UCBStorageElementList_Impl; + +class UCBStorage_Impl : public SvRefBase +{ + virtual ~UCBStorage_Impl() override; +public: + UCBStorage* m_pAntiImpl; // only valid if external references exists + + OUString m_aName; // the actual name ( changed with a Rename command at the parent ) + OUString m_aURL; // the full path name to create the content + OUString m_aContentType; + OUString m_aOriginalContentType; + std::unique_ptr<::ucbhelper::Content> m_pContent; // the content that provides the storage elements + std::unique_ptr<::utl::TempFile> m_pTempFile; // temporary file, only for storages on stream + SvStream* m_pSource; // original stream, only for storages on a stream + ErrCode m_nError; + StreamMode m_nMode; // open mode ( read/write/trunc/nocreate/sharing ) + bool m_bCommited; // sending the streams is coordinated by the root storage of the package + bool m_bDirect; // the storage and its streams are opened in direct mode; for UCBStorages + // this means that the root storage does an autocommit when its external + // reference is destroyed + bool m_bIsRoot; // marks this storage as root storages that manages all commits and reverts + bool m_bIsLinked; + bool m_bListCreated; + SotClipboardFormatId m_nFormat; + OUString m_aUserTypeName; + SvGlobalName m_aClassId; + + UCBStorageElementList_Impl m_aChildrenList; + + bool m_bRepairPackage; + Reference< XProgressHandler > m_xProgressHandler; + + UCBStorage_Impl( const ::ucbhelper::Content&, const OUString&, StreamMode, UCBStorage*, bool, + bool, bool = false, Reference< XProgressHandler > const & = Reference< XProgressHandler >() ); + UCBStorage_Impl( const OUString&, StreamMode, UCBStorage*, bool, bool, + bool, Reference< XProgressHandler > const & ); + UCBStorage_Impl( SvStream&, UCBStorage*, bool ); + void Init(); + sal_Int16 Commit(); + void Revert(); + bool Insert( ::ucbhelper::Content *pContent ); + UCBStorage_Impl* OpenStorage( UCBStorageElement_Impl* pElement, StreamMode nMode, bool bDirect ); + void OpenStream( UCBStorageElement_Impl*, StreamMode, bool ); + void SetProps( const Sequence < Sequence < PropertyValue > >& rSequence, const OUString& ); + void GetProps( sal_Int32&, Sequence < Sequence < PropertyValue > >& rSequence, const OUString& ); + sal_Int32 GetObjectCount(); + void ReadContent(); + void CreateContent(); + ::ucbhelper::Content* GetContent() + { + if ( !m_pContent ) + CreateContent(); + return m_pContent.get(); + } + UCBStorageElementList_Impl& GetChildrenList() + { + const ErrCode nError = m_nError; + ReadContent(); + if ( m_nMode & StreamMode::WRITE ) + { + m_nError = nError; + if ( m_pAntiImpl ) + { + m_pAntiImpl->ResetError(); + m_pAntiImpl->SetError( nError ); + } + } + return m_aChildrenList; + } + + void SetError( ErrCode nError ); +}; + +typedef tools::SvRef<UCBStorage_Impl> UCBStorage_ImplRef; + +// this struct contains all necessary information on an element inside a UCBStorage +struct UCBStorageElement_Impl +{ + OUString m_aName; // the actual URL relative to the root "folder" + OUString m_aOriginalName;// the original name in the content + sal_uInt64 m_nSize; + bool m_bIsFolder; // Only true when it is a UCBStorage ! + bool m_bIsStorage; // Also true when it is an OLEStorage ! + bool m_bIsRemoved; // element will be removed on commit + bool m_bIsInserted; // element will be removed on revert + UCBStorage_ImplRef m_xStorage; // reference to the "real" storage + UCBStorageStream_ImplRef m_xStream; // reference to the "real" stream + + UCBStorageElement_Impl( const OUString& rName, + bool bIsFolder = false, sal_uInt64 nSize = 0 ) + : m_aName( rName ) + , m_aOriginalName( rName ) + , m_nSize( nSize ) + , m_bIsFolder( bIsFolder ) + , m_bIsStorage( bIsFolder ) + , m_bIsRemoved( false ) + , m_bIsInserted( false ) + { + } + + ::ucbhelper::Content* GetContent(); + bool IsModified() const; + OUString GetContentType() const; + void SetContentType( const OUString& ); + OUString GetOriginalContentType() const; + bool IsLoaded() const + { return m_xStream.is() || m_xStorage.is(); } +}; + +::ucbhelper::Content* UCBStorageElement_Impl::GetContent() +{ + if ( m_xStream.is() ) + return m_xStream->m_pContent; + else if ( m_xStorage.is() ) + return m_xStorage->GetContent(); + else + return nullptr; +} + +OUString UCBStorageElement_Impl::GetContentType() const +{ + if ( m_xStream.is() ) + return m_xStream->m_aContentType; + else if ( m_xStorage.is() ) + return m_xStorage->m_aContentType; + else + { + OSL_FAIL("Element not loaded!"); + return OUString(); + } +} + +void UCBStorageElement_Impl::SetContentType( const OUString& rType ) +{ + if ( m_xStream.is() ) { + m_xStream->m_aContentType = m_xStream->m_aOriginalContentType = rType; + } + else if ( m_xStorage.is() ) { + m_xStorage->m_aContentType = m_xStorage->m_aOriginalContentType = rType; + } + else { + OSL_FAIL("Element not loaded!"); + } +} + +OUString UCBStorageElement_Impl::GetOriginalContentType() const +{ + if ( m_xStream.is() ) + return m_xStream->m_aOriginalContentType; + else if ( m_xStorage.is() ) + return m_xStorage->m_aOriginalContentType; + else + return OUString(); +} + +bool UCBStorageElement_Impl::IsModified() const +{ + bool bModified = m_bIsRemoved || m_bIsInserted || m_aName != m_aOriginalName; + if ( bModified ) + { + if ( m_xStream.is() ) + bModified = m_xStream->m_aContentType != m_xStream->m_aOriginalContentType; + else if ( m_xStorage.is() ) + bModified = m_xStorage->m_aContentType != m_xStorage->m_aOriginalContentType; + } + + return bModified; +} + +UCBStorageStream_Impl::UCBStorageStream_Impl( const OUString& rName, StreamMode nMode, UCBStorageStream* pStream, bool bDirect, bool bRepair, Reference< XProgressHandler > const & xProgress ) + : m_pAntiImpl( pStream ) + , m_aURL( rName ) + , m_pContent( nullptr ) + , m_nError( ERRCODE_NONE ) + , m_nMode( nMode ) + , m_bSourceRead( !( nMode & StreamMode::TRUNC ) ) + , m_bModified( false ) + , m_bCommited( false ) + , m_bDirect( bDirect ) + , m_bIsOLEStorage( false ) +{ + // name is last segment in URL + INetURLObject aObj( rName ); + m_aName = m_aOriginalName = aObj.GetLastName(); + try + { + // create the content + Reference< css::ucb::XCommandEnvironment > xComEnv; + + OUString aTemp( rName ); + + if ( bRepair ) + { + xComEnv = new ::ucbhelper::CommandEnvironment( Reference< css::task::XInteractionHandler >(), xProgress ); + aTemp += "?repairpackage"; + } + + m_pContent = new ::ucbhelper::Content( aTemp, xComEnv, comphelper::getProcessComponentContext() ); + } + catch (const ContentCreationException&) + { + // content could not be created + SetError( SVSTREAM_CANNOT_MAKE ); + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + } +} + +UCBStorageStream_Impl::~UCBStorageStream_Impl() +{ + if( m_rSource.is() ) + m_rSource.clear(); + + m_pStream.reset(); + + if (!m_aTempURL.isEmpty()) + osl::File::remove(m_aTempURL); + + delete m_pContent; +} + + +bool UCBStorageStream_Impl::Init() +{ + if( !m_pStream ) + { + // no temporary stream was created + // create one + + if ( m_aTempURL.isEmpty() ) + m_aTempURL = ::utl::TempFile().GetURL(); + + m_pStream = ::utl::UcbStreamHelper::CreateStream( m_aTempURL, StreamMode::STD_READWRITE, true /* bFileExists */ ); +#if OSL_DEBUG_LEVEL > 0 + ++nOpenFiles; +#endif + + if( !m_pStream ) + { + OSL_FAIL( "Suspicious temporary stream creation!" ); + SetError( SVSTREAM_CANNOT_MAKE ); + return false; + } + + SetError( m_pStream->GetError() ); + } + + if( m_bSourceRead && !m_rSource.is() ) + { + // source file contain useful information and is not opened + // open it from the point of noncopied data + + try + { + m_rSource = m_pContent->openStream(); + } + catch (const Exception&) + { + // usually means that stream could not be opened + } + + if( m_rSource.is() ) + { + m_pStream->Seek( STREAM_SEEK_TO_END ); + + try + { + m_rSource->skipBytes( m_pStream->Tell() ); + } + catch (const BufferSizeExceededException&) + { + // the temporary stream already contain all the data + m_bSourceRead = false; + } + catch (const Exception&) + { + // something is really wrong + m_bSourceRead = false; + OSL_FAIL( "Can not operate original stream!" ); + SetError( SVSTREAM_CANNOT_MAKE ); + } + + m_pStream->Seek( 0 ); + } + else + { + // if the new file is edited then no source exist + m_bSourceRead = false; + //SetError( SVSTREAM_CANNOT_MAKE ); + } + } + + DBG_ASSERT( m_rSource.is() || !m_bSourceRead, "Unreadable source stream!" ); + + return true; +} + +void UCBStorageStream_Impl::ReadSourceWriteTemporary() +{ + // read source stream till the end and copy all the data to + // the current position of the temporary stream + + if( m_bSourceRead ) + { + Sequence<sal_Int8> aData(32000); + + try + { + sal_Int32 aReaded; + do + { + aReaded = m_rSource->readBytes( aData, 32000 ); + m_pStream->WriteBytes(aData.getConstArray(), aReaded); + } while( aReaded == 32000 ); + } + catch (const Exception &) + { + TOOLS_WARN_EXCEPTION("sot", ""); + } + } + + m_bSourceRead = false; +} + +sal_uInt64 UCBStorageStream_Impl::ReadSourceWriteTemporary(sal_uInt64 aLength) +{ + // read aLength bite from the source stream and copy them to the current + // position of the temporary stream + + sal_uInt64 aResult = 0; + + if( m_bSourceRead ) + { + Sequence<sal_Int8> aData(32000); + + try + { + + sal_Int32 aReaded = 32000; + + for (sal_uInt64 nInd = 0; nInd < aLength && aReaded == 32000 ; nInd += 32000) + { + sal_Int32 aToCopy = std::min<sal_Int32>( aLength - nInd, 32000 ); + aReaded = m_rSource->readBytes( aData, aToCopy ); + aResult += m_pStream->WriteBytes(aData.getConstArray(), aReaded); + } + + if( aResult < aLength ) + m_bSourceRead = false; + } + catch( const Exception & ) + { + TOOLS_WARN_EXCEPTION("sot", ""); + } + } + + return aResult; +} + +void UCBStorageStream_Impl::CopySourceToTemporary() +{ + // current position of the temporary stream is not changed + if( m_bSourceRead ) + { + sal_uInt64 aPos = m_pStream->Tell(); + m_pStream->Seek( STREAM_SEEK_TO_END ); + ReadSourceWriteTemporary(); + m_pStream->Seek( aPos ); + } +} + +// UCBStorageStream_Impl must have a SvStream interface, because it then can be used as underlying stream +// of an OLEStorage; so every write access caused by storage operations marks the UCBStorageStream as modified +std::size_t UCBStorageStream_Impl::GetData(void* pData, std::size_t const nSize) +{ + std::size_t aResult = 0; + + if( !Init() ) + return 0; + + + // read data that is in temporary stream + aResult = m_pStream->ReadBytes( pData, nSize ); + if( m_bSourceRead && aResult < nSize ) + { + // read the tail of the data from original stream + // copy this tail to the temporary stream + + std::size_t aToRead = nSize - aResult; + pData = static_cast<void*>( static_cast<char*>(pData) + aResult ); + + try + { + Sequence<sal_Int8> aData( aToRead ); + std::size_t aReaded = m_rSource->readBytes( aData, aToRead ); + aResult += m_pStream->WriteBytes(static_cast<const void*>(aData.getConstArray()), aReaded); + memcpy( pData, aData.getArray(), aReaded ); + } + catch (const Exception &) + { + TOOLS_WARN_EXCEPTION("sot", ""); + } + + if( aResult < nSize ) + m_bSourceRead = false; + } + + return aResult; +} + +std::size_t UCBStorageStream_Impl::PutData(const void* pData, std::size_t const nSize) +{ + if ( !(m_nMode & StreamMode::WRITE) ) + { + SetError( ERRCODE_IO_ACCESSDENIED ); + return 0; // ?mav? + } + + if( !nSize || !Init() ) + return 0; + + std::size_t aResult = m_pStream->WriteBytes( pData, nSize ); + + m_bModified = aResult > 0; + + return aResult; + +} + +sal_uInt64 UCBStorageStream_Impl::SeekPos(sal_uInt64 const nPos) +{ + // check if a truncated STREAM_SEEK_TO_END was passed + assert(nPos != SAL_MAX_UINT32); + + if( !Init() ) + return 0; + + sal_uInt64 aResult; + + if( nPos == STREAM_SEEK_TO_END ) + { + m_pStream->Seek( STREAM_SEEK_TO_END ); + ReadSourceWriteTemporary(); + aResult = m_pStream->Tell(); + } + else + { + // the problem is that even if nPos is larger the length + // of the stream, the stream pointer will be moved to this position + // so we have to check if temporary stream does not contain required position + + if( m_pStream->Tell() > nPos + || m_pStream->Seek( STREAM_SEEK_TO_END ) > nPos ) + { + // no copying is required + aResult = m_pStream->Seek( nPos ); + } + else + { + // the temp stream pointer points to the end now + aResult = m_pStream->Tell(); + + if( aResult < nPos ) + { + if( m_bSourceRead ) + { + aResult += ReadSourceWriteTemporary( nPos - aResult ); + if( aResult < nPos ) + m_bSourceRead = false; + + DBG_ASSERT( aResult == m_pStream->Tell(), "Error in stream arithmetic!\n" ); + } + + if( (m_nMode & StreamMode::WRITE) && !m_bSourceRead && aResult < nPos ) + { + // it means that all the Source stream was copied already + // but the required position still was not reached + // for writable streams it should be done + m_pStream->SetStreamSize( nPos ); + aResult = m_pStream->Seek( STREAM_SEEK_TO_END ); + DBG_ASSERT( aResult == nPos, "Error in stream arithmetic!\n" ); + } + } + } + } + + return aResult; +} + +void UCBStorageStream_Impl::SetSize(sal_uInt64 const nSize) +{ + if ( !(m_nMode & StreamMode::WRITE) ) + { + SetError( ERRCODE_IO_ACCESSDENIED ); + return; + } + + if( !Init() ) + return; + + m_bModified = true; + + if( m_bSourceRead ) + { + sal_uInt64 const aPos = m_pStream->Tell(); + m_pStream->Seek( STREAM_SEEK_TO_END ); + if( m_pStream->Tell() < nSize ) + ReadSourceWriteTemporary( nSize - m_pStream->Tell() ); + m_pStream->Seek( aPos ); + } + + m_pStream->SetStreamSize( nSize ); + m_bSourceRead = false; +} + +void UCBStorageStream_Impl::FlushData() +{ + if( m_pStream ) + { + CopySourceToTemporary(); + m_pStream->Flush(); + } + + m_bCommited = true; +} + +void UCBStorageStream_Impl::SetError( ErrCode nErr ) +{ + if ( !m_nError ) + { + m_nError = nErr; + SvStream::SetError( nErr ); + if ( m_pAntiImpl ) m_pAntiImpl->SetError( nErr ); + } +} + +void UCBStorageStream_Impl::ResetError() +{ + m_nError = ERRCODE_NONE; + SvStream::ResetError(); + if ( m_pAntiImpl ) + m_pAntiImpl->ResetError(); +} + +sal_uInt64 UCBStorageStream_Impl::GetSize() +{ + if( !Init() ) + return 0; + + sal_uInt64 nPos = m_pStream->Tell(); + m_pStream->Seek( STREAM_SEEK_TO_END ); + ReadSourceWriteTemporary(); + sal_uInt64 nRet = m_pStream->Tell(); + m_pStream->Seek( nPos ); + + return nRet; +} + +BaseStorage* UCBStorageStream_Impl::CreateStorage() +{ + // create an OLEStorage on a SvStream ( = this ) + // it gets the root attribute because otherwise it would probably not write before my root is committed + UCBStorageStream* pNewStorageStream = new UCBStorageStream( this ); + Storage *pStorage = new Storage( *pNewStorageStream, m_bDirect ); + + // GetError() call clears error code for OLE storages, must be changed in future + const ErrCode nTmpErr = pStorage->GetError(); + pStorage->SetError( nTmpErr ); + + m_bIsOLEStorage = !nTmpErr; + return static_cast< BaseStorage* > ( pStorage ); +} + +sal_Int16 UCBStorageStream_Impl::Commit() +{ + // send stream to the original content + // the parent storage is responsible for the correct handling of deleted contents + if ( m_bCommited || m_bIsOLEStorage || m_bDirect ) + { + // modified streams with OLEStorages on it have autocommit; it is assumed that the OLEStorage + // was committed as well ( if not opened in direct mode ) + + if ( m_bModified ) + { + try + { + CopySourceToTemporary(); + + // release all stream handles + Free(); + + // the temporary file does not exist only for truncated streams + DBG_ASSERT( !m_aTempURL.isEmpty() || ( m_nMode & StreamMode::TRUNC ), "No temporary file to read from!"); + if ( m_aTempURL.isEmpty() && !( m_nMode & StreamMode::TRUNC ) ) + throw RuntimeException(); + + // create wrapper to stream that is only used while reading inside package component + Reference < XInputStream > xStream = new FileStreamWrapper_Impl( m_aTempURL ); + + InsertCommandArgument aArg; + aArg.Data = xStream; + aArg.ReplaceExisting = true; + m_pContent->executeCommand( "insert", Any(aArg) ); + + // wrapper now controls lifetime of temporary file + m_aTempURL.clear(); + + INetURLObject aObj( m_aURL ); + aObj.setName( m_aName ); + m_aURL = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + m_bModified = false; + m_bSourceRead = true; + } + catch (const CommandAbortedException&) + { + // any command wasn't executed successfully - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const Exception&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + + m_bCommited = false; + return COMMIT_RESULT_SUCCESS; + } + } + + return COMMIT_RESULT_NOTHING_TO_DO; +} + +void UCBStorageStream_Impl::Revert() +{ + // if an OLEStorage is created on this stream, no "revert" is necessary because OLEStorages do nothing on "Revert" ! + if ( m_bCommited ) + { + OSL_FAIL("Revert while commit is in progress!" ); + return; // ??? + } + + Free(); + if ( !m_aTempURL.isEmpty() ) + { + osl::File::remove(m_aTempURL); + m_aTempURL.clear(); + } + + m_bSourceRead = false; + try + { + m_rSource = m_pContent->openStream(); + if( m_rSource.is() ) + { + if ( m_pAntiImpl && ( m_nMode & StreamMode::TRUNC ) ) + // stream is in use and should be truncated + m_bSourceRead = false; + else + { + m_nMode &= ~StreamMode::TRUNC; + m_bSourceRead = true; + } + } + else + SetError( SVSTREAM_CANNOT_MAKE ); + } + catch (const ContentCreationException&) + { + SetError( ERRCODE_IO_GENERAL ); + } + catch (const RuntimeException&) + { + SetError( ERRCODE_IO_GENERAL ); + } + catch (const Exception&) + { + } + + m_bModified = false; + m_aName = m_aOriginalName; + m_aContentType = m_aOriginalContentType; +} + +bool UCBStorageStream_Impl::Clear() +{ + bool bRet = ( m_pAntiImpl == nullptr ); + DBG_ASSERT( bRet, "Removing used stream!" ); + if( bRet ) + { + Free(); + } + + return bRet; +} + +void UCBStorageStream_Impl::Free() +{ +#if OSL_DEBUG_LEVEL > 0 + if ( m_pStream ) + { + if ( !m_aTempURL.isEmpty() ) + --nOpenFiles; + else + --nOpenStreams; + } +#endif + + m_rSource.clear(); + m_pStream.reset(); +} + +void UCBStorageStream_Impl::PrepareCachedForReopen( StreamMode nMode ) +{ + bool isWritable = bool( m_nMode & StreamMode::WRITE ); + if ( isWritable ) + { + // once stream was writable, never reset to readonly + nMode |= StreamMode::WRITE; + } + + m_nMode = nMode; + Free(); + + if ( nMode & StreamMode::TRUNC ) + { + m_bSourceRead = false; // usually it should be 0 already but just in case... + + if ( !m_aTempURL.isEmpty() ) + { + osl::File::remove(m_aTempURL); + m_aTempURL.clear(); + } + } +} + +UCBStorageStream::UCBStorageStream( const OUString& rName, StreamMode nMode, bool bDirect, bool bRepair, Reference< XProgressHandler > const & xProgress ) +{ + // pImp must be initialized in the body, because otherwise the vtable of the stream is not initialized + // to class UCBStorageStream ! + pImp = new UCBStorageStream_Impl( rName, nMode, this, bDirect, bRepair, xProgress ); + pImp->AddFirstRef(); // use direct refcounting because in header file only a pointer should be used + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorageStream::UCBStorageStream( UCBStorageStream_Impl *pImpl ) + : pImp( pImpl ) +{ + pImp->AddFirstRef(); // use direct refcounting because in header file only a pointer should be used + pImp->m_pAntiImpl = this; + SetError( pImp->m_nError ); + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorageStream::~UCBStorageStream() +{ + if ( pImp->m_nMode & StreamMode::WRITE ) + pImp->Flush(); + pImp->m_pAntiImpl = nullptr; + pImp->Free(); + pImp->ReleaseRef(); +} + +sal_Int32 UCBStorageStream::Read( void * pData, sal_Int32 nSize ) +{ + //return pImp->m_pStream->Read( pData, nSize ); + return pImp->GetData( pData, nSize ); +} + +sal_Int32 UCBStorageStream::Write( const void* pData, sal_Int32 nSize ) +{ + return pImp->PutData( pData, nSize ); +} + +sal_uInt64 UCBStorageStream::Seek( sal_uInt64 nPos ) +{ + //return pImp->m_pStream->Seek( nPos ); + return pImp->Seek( nPos ); +} + +sal_uInt64 UCBStorageStream::Tell() +{ + if( !pImp->Init() ) + return 0; + return pImp->m_pStream->Tell(); +} + +void UCBStorageStream::Flush() +{ + // streams are never really transacted, so flush also means commit ! + Commit(); +} + +bool UCBStorageStream::SetSize( sal_uInt64 nNewSize ) +{ + pImp->SetSize( nNewSize ); + return !pImp->GetError(); +} + +bool UCBStorageStream::Validate( bool bWrite ) const +{ + return ( !bWrite || ( pImp->m_nMode & StreamMode::WRITE ) ); +} + +bool UCBStorageStream::ValidateMode( StreamMode m ) const +{ + // ??? + if( m == ( StreamMode::READ | StreamMode::TRUNC ) ) // from stg.cxx + return true; + if( ( m & StreamMode::READWRITE) == StreamMode::READ ) + { + // only SHARE_DENYWRITE or SHARE_DENYALL allowed + if( ( m & StreamMode::SHARE_DENYWRITE ) + || ( m & StreamMode::SHARE_DENYALL ) ) + return true; + } + else + { + // only SHARE_DENYALL allowed + // storages open in r/o mode are OK, since only + // the commit may fail + if( m & StreamMode::SHARE_DENYALL ) + return true; + } + + return true; +} + +SvStream* UCBStorageStream::GetModifySvStream() +{ + return static_cast<SvStream*>(pImp); +} + +bool UCBStorageStream::Equals( const BaseStorageStream& rStream ) const +{ + // ??? + return static_cast<BaseStorageStream const *>(this) == &rStream; +} + +bool UCBStorageStream::Commit() +{ + // mark this stream for sending it on root commit + pImp->FlushData(); + return true; +} + +void UCBStorageStream::CopyTo( BaseStorageStream* pDestStm ) +{ + if( !pImp->Init() ) + return; + + UCBStorageStream* pStg = dynamic_cast<UCBStorageStream*>( pDestStm ); + if ( pStg ) + pStg->pImp->m_aContentType = pImp->m_aContentType; + + pDestStm->SetSize( 0 ); + Seek( STREAM_SEEK_TO_END ); + sal_Int32 n = Tell(); + if( n < 0 ) + return; + + if( !pDestStm->SetSize( n ) || !n ) + return; + + std::unique_ptr<sal_uInt8[]> p(new sal_uInt8[ 4096 ]); + Seek( 0 ); + pDestStm->Seek( 0 ); + while( n ) + { + sal_Int32 nn = n; + if( nn > 4096 ) + nn = 4096; + if( Read( p.get(), nn ) != nn ) + break; + if( pDestStm->Write( p.get(), nn ) != nn ) + break; + n -= nn; + } +} + +bool UCBStorageStream::SetProperty( const OUString& rName, const css::uno::Any& rValue ) +{ + if ( rName == "Title") + return false; + + if ( rName == "MediaType") + { + OUString aTmp; + rValue >>= aTmp; + pImp->m_aContentType = aTmp; + } + + try + { + if ( pImp->m_pContent ) + { + pImp->m_pContent->setPropertyValue( rName, rValue ); + return true; + } + } + catch (const Exception&) + { + } + + return false; +} + +sal_uInt64 UCBStorageStream::GetSize() const +{ + return pImp->GetSize(); +} + +UCBStorage::UCBStorage( SvStream& rStrm, bool bDirect ) +{ + // pImp must be initialized in the body, because otherwise the vtable of the stream is not initialized + // to class UCBStorage ! + pImp = new UCBStorage_Impl( rStrm, this, bDirect ); + + pImp->AddFirstRef(); + pImp->Init(); + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorage::UCBStorage( const ::ucbhelper::Content& rContent, const OUString& rName, StreamMode nMode, bool bDirect, bool bIsRoot ) +{ + // pImp must be initialized in the body, because otherwise the vtable of the stream is not initialized + // to class UCBStorage ! + pImp = new UCBStorage_Impl( rContent, rName, nMode, this, bDirect, bIsRoot ); + pImp->AddFirstRef(); + pImp->Init(); + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorage::UCBStorage( const OUString& rName, StreamMode nMode, bool bDirect, bool bIsRoot, bool bIsRepair, Reference< XProgressHandler > const & xProgressHandler ) +{ + // pImp must be initialized in the body, because otherwise the vtable of the stream is not initialized + // to class UCBStorage ! + pImp = new UCBStorage_Impl( rName, nMode, this, bDirect, bIsRoot, bIsRepair, xProgressHandler ); + pImp->AddFirstRef(); + pImp->Init(); + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorage::UCBStorage( const OUString& rName, StreamMode nMode, bool bDirect, bool bIsRoot ) +{ + // pImp must be initialized in the body, because otherwise the vtable of the stream is not initialized + // to class UCBStorage ! + pImp = new UCBStorage_Impl( rName, nMode, this, bDirect, bIsRoot, false, Reference< XProgressHandler >() ); + pImp->AddFirstRef(); + pImp->Init(); + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorage::UCBStorage( UCBStorage_Impl *pImpl ) + : pImp( pImpl ) +{ + pImp->m_pAntiImpl = this; + SetError( pImp->m_nError ); + pImp->AddFirstRef(); // use direct refcounting because in header file only a pointer should be used + StorageBase::m_nMode = pImp->m_nMode; +} + +UCBStorage::~UCBStorage() +{ + if ( pImp->m_bIsRoot && pImp->m_bDirect && ( !pImp->m_pTempFile || pImp->m_pSource ) ) + // DirectMode is simulated with an AutoCommit + Commit(); + + pImp->m_pAntiImpl = nullptr; + pImp->ReleaseRef(); +} + +UCBStorage_Impl::UCBStorage_Impl( const ::ucbhelper::Content& rContent, const OUString& rName, StreamMode nMode, UCBStorage* pStorage, bool bDirect, bool bIsRoot, bool bIsRepair, Reference< XProgressHandler > const & xProgressHandler ) + : m_pAntiImpl( pStorage ) + , m_pContent( new ::ucbhelper::Content( rContent ) ) + , m_pSource( nullptr ) + //, m_pStream( NULL ) + , m_nError( ERRCODE_NONE ) + , m_nMode( nMode ) + , m_bCommited( false ) + , m_bDirect( bDirect ) + , m_bIsRoot( bIsRoot ) + , m_bIsLinked( true ) + , m_bListCreated( false ) + , m_nFormat( SotClipboardFormatId::NONE ) + , m_bRepairPackage( bIsRepair ) + , m_xProgressHandler( xProgressHandler ) +{ + OUString aName( rName ); + if( aName.isEmpty() ) + { + // no name given = use temporary name! + DBG_ASSERT( m_bIsRoot, "SubStorage must have a name!" ); + m_pTempFile.reset(new ::utl::TempFile); + m_pTempFile->EnableKillingFile(); + m_aName = aName = m_pTempFile->GetURL(); + } + + m_aURL = rName; +} + +UCBStorage_Impl::UCBStorage_Impl( const OUString& rName, StreamMode nMode, UCBStorage* pStorage, bool bDirect, bool bIsRoot, bool bIsRepair, Reference< XProgressHandler > const & xProgressHandler ) + : m_pAntiImpl( pStorage ) + , m_pSource( nullptr ) + //, m_pStream( NULL ) + , m_nError( ERRCODE_NONE ) + , m_nMode( nMode ) + , m_bCommited( false ) + , m_bDirect( bDirect ) + , m_bIsRoot( bIsRoot ) + , m_bIsLinked( false ) + , m_bListCreated( false ) + , m_nFormat( SotClipboardFormatId::NONE ) + , m_bRepairPackage( bIsRepair ) + , m_xProgressHandler( xProgressHandler ) +{ + OUString aName( rName ); + if( aName.isEmpty() ) + { + // no name given = use temporary name! + DBG_ASSERT( m_bIsRoot, "SubStorage must have a name!" ); + m_pTempFile.reset(new ::utl::TempFile); + m_pTempFile->EnableKillingFile(); + m_aName = aName = m_pTempFile->GetURL(); + } + + if ( m_bIsRoot ) + { + // create the special package URL for the package content + m_aURL = "vnd.sun.star.pkg://" + + INetURLObject::encode( aName, INetURLObject::PART_AUTHORITY, INetURLObject::EncodeMechanism::All ); + + if ( m_nMode & StreamMode::WRITE ) + { + // the root storage opens the package, so make sure that there is any + ::utl::UcbStreamHelper::CreateStream( aName, StreamMode::STD_READWRITE, m_pTempFile != nullptr /* bFileExists */ ); + } + } + else + { + // substorages are opened like streams: the URL is a "child URL" of the root package URL + m_aURL = rName; + if ( !m_aURL.startsWith( "vnd.sun.star.pkg://") ) + m_bIsLinked = true; + } +} + +UCBStorage_Impl::UCBStorage_Impl( SvStream& rStream, UCBStorage* pStorage, bool bDirect ) + : m_pAntiImpl( pStorage ) + , m_pTempFile( new ::utl::TempFile ) + , m_pSource( &rStream ) + , m_nError( ERRCODE_NONE ) + , m_bCommited( false ) + , m_bDirect( bDirect ) + , m_bIsRoot( true ) + , m_bIsLinked( false ) + , m_bListCreated( false ) + , m_nFormat( SotClipboardFormatId::NONE ) + , m_bRepairPackage( false ) +{ + // opening in direct mode is too fuzzy because the data is transferred to the stream in the Commit() call, + // which will be called in the storages' dtor + m_pTempFile->EnableKillingFile(); + DBG_ASSERT( !bDirect, "Storage on a stream must not be opened in direct mode!" ); + + // UCBStorages work on a content, so a temporary file for a content must be created, even if the stream is only + // accessed readonly + // the root storage opens the package; create the special package URL for the package content + m_aURL = "vnd.sun.star.pkg://" + + INetURLObject::encode( m_pTempFile->GetURL(), INetURLObject::PART_AUTHORITY, INetURLObject::EncodeMechanism::All ); + + // copy data into the temporary file + std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( m_pTempFile->GetURL(), StreamMode::STD_READWRITE, true /* bFileExists */ )); + if ( pStream ) + { + rStream.Seek(0); + rStream.ReadStream( *pStream ); + pStream->Flush(); + pStream.reset(); + } + + // close stream and let content access the file + m_pSource->Seek(0); + + // check opening mode + m_nMode = StreamMode::READ; + if( rStream.IsWritable() ) + m_nMode = StreamMode::READ | StreamMode::WRITE; +} + +void UCBStorage_Impl::Init() +{ + // name is last segment in URL + INetURLObject aObj( m_aURL ); + if ( m_aName.isEmpty() ) + // if the name was not already set to a temp name + m_aName = aObj.GetLastName(); + + if ( !m_pContent ) + CreateContent(); + + if ( m_pContent ) + { + if ( m_bIsLinked ) + { + if( m_bIsRoot ) + { + ReadContent(); + if ( m_nError == ERRCODE_NONE ) + { + // read the manifest.xml file + aObj.Append( u"META-INF" ); + aObj.Append( u"manifest.xml" ); + + // create input stream + std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::STD_READ )); + // no stream means no manifest.xml + if ( pStream ) + { + if ( !pStream->GetError() ) + { + rtl::Reference<::utl::OInputStreamWrapper> pHelper = new ::utl::OInputStreamWrapper( *pStream ); + + // create a manifest reader object that will read in the manifest from the stream + Reference < css::packages::manifest::XManifestReader > xReader = + css::packages::manifest::ManifestReader::create( + ::comphelper::getProcessComponentContext() ) ; + Sequence < Sequence < PropertyValue > > aProps = xReader->readManifestSequence( pHelper ); + + // cleanup + xReader = nullptr; + pHelper = nullptr; + SetProps( aProps, OUString() ); + } + } + } + } + else + ReadContent(); + } + else + { + // get the manifest information from the package + try { + Any aAny = m_pContent->getPropertyValue("MediaType"); + OUString aTmp; + if ( ( aAny >>= aTmp ) && !aTmp.isEmpty() ) + m_aContentType = m_aOriginalContentType = aTmp; + } + catch (const Exception&) + { + SAL_WARN( "sot", + "getPropertyValue has thrown an exception! Please let developers know the scenario!" ); + } + } + } + + if ( m_aContentType.isEmpty() ) + return; + + // get the clipboard format using the content type + css::datatransfer::DataFlavor aDataFlavor; + aDataFlavor.MimeType = m_aContentType; + m_nFormat = SotExchange::GetFormat( aDataFlavor ); + + // get the ClassId using the clipboard format ( internal table ) + m_aClassId = GetClassId_Impl( m_nFormat ); + + // get human presentable name using the clipboard format + SotExchange::GetFormatDataFlavor( m_nFormat, aDataFlavor ); + m_aUserTypeName = aDataFlavor.HumanPresentableName; + + if( m_pContent && !m_bIsLinked && m_aClassId != SvGlobalName() ) + ReadContent(); +} + +void UCBStorage_Impl::CreateContent() +{ + try + { + // create content; where to put StreamMode ?! ( already done when opening the file of the package ? ) + Reference< css::ucb::XCommandEnvironment > xComEnv; + + OUString aTemp( m_aURL ); + + if ( m_bRepairPackage ) + { + xComEnv = new ::ucbhelper::CommandEnvironment( Reference< css::task::XInteractionHandler >(), + m_xProgressHandler ); + aTemp += "?repairpackage"; + } + + m_pContent.reset(new ::ucbhelper::Content( aTemp, xComEnv, comphelper::getProcessComponentContext() )); + } + catch (const ContentCreationException&) + { + // content could not be created + SetError( SVSTREAM_CANNOT_MAKE ); + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( SVSTREAM_CANNOT_MAKE ); + } +} + +void UCBStorage_Impl::ReadContent() +{ + if ( m_bListCreated ) + return; + + m_bListCreated = true; + + try + { + GetContent(); + if ( !m_pContent ) + return; + + // create cursor for access to children + Reference< XResultSet > xResultSet = m_pContent->createCursor( { "Title", "IsFolder", "MediaType", "Size" }, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS ); + Reference< XRow > xRow( xResultSet, UNO_QUERY ); + if ( xResultSet.is() ) + { + while ( xResultSet->next() ) + { + // insert all into the children list + OUString aTitle( xRow->getString(1) ); + if ( m_bIsLinked ) + { + // unpacked storages have to deal with the meta-inf folder by themselves + if ( aTitle == "META-INF" ) + continue; + } + + bool bIsFolder( xRow->getBoolean(2) ); + sal_Int64 nSize = xRow->getLong(4); + UCBStorageElement_Impl* pElement = new UCBStorageElement_Impl( aTitle, bIsFolder, nSize ); + m_aChildrenList.emplace_back( pElement ); + + bool bIsOfficeDocument = m_bIsLinked || ( m_aClassId != SvGlobalName() ); + if ( bIsFolder ) + { + if ( m_bIsLinked ) + OpenStorage( pElement, m_nMode, m_bDirect ); + if ( pElement->m_xStorage.is() ) + pElement->m_xStorage->Init(); + } + else if ( bIsOfficeDocument ) + { + // streams can be external OLE objects, so they are now folders, but storages! + OUString aName( m_aURL + "/" + xRow->getString(1)); + + Reference< css::ucb::XCommandEnvironment > xComEnv; + if ( m_bRepairPackage ) + { + xComEnv = new ::ucbhelper::CommandEnvironment( Reference< css::task::XInteractionHandler >(), + m_xProgressHandler ); + aName += "?repairpackage"; + } + + ::ucbhelper::Content aContent( aName, xComEnv, comphelper::getProcessComponentContext() ); + + OUString aMediaType; + Any aAny = aContent.getPropertyValue("MediaType"); + if ( ( aAny >>= aMediaType ) && ( aMediaType == "application/vnd.sun.star.oleobject" ) ) + pElement->m_bIsStorage = true; + else if ( aMediaType.isEmpty() ) + { + // older files didn't have that special content type, so they must be detected + OpenStream( pElement, StreamMode::STD_READ, m_bDirect ); + if ( Storage::IsStorageFile( pElement->m_xStream.get() ) ) + pElement->m_bIsStorage = true; + else + pElement->m_xStream->Free(); + } + } + } + } + } + catch (const InteractiveIOException& r) + { + if ( r.Code != IOErrorCode_NOT_EXISTING ) + SetError( ERRCODE_IO_GENERAL ); + } + catch (const CommandAbortedException&) + { + // any command wasn't executed successfully - not specified + if ( !( m_nMode & StreamMode::WRITE ) ) + // if the folder was just inserted and not already committed, this is not an error! + SetError( ERRCODE_IO_GENERAL ); + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + } + catch (const ResultSetException&) + { + // means that the package file is broken + SetError( ERRCODE_IO_BROKENPACKAGE ); + } + catch (const SQLException&) + { + // means that the file can be broken + SetError( ERRCODE_IO_WRONGFORMAT ); + } + catch (const Exception&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + } +} + +void UCBStorage_Impl::SetError( ErrCode nError ) +{ + if ( !m_nError ) + { + m_nError = nError; + if ( m_pAntiImpl ) m_pAntiImpl->SetError( nError ); + } +} + +sal_Int32 UCBStorage_Impl::GetObjectCount() +{ + sal_Int32 nCount = m_aChildrenList.size(); + for (auto& pElement : m_aChildrenList) + { + DBG_ASSERT( !pElement->m_bIsFolder || pElement->m_xStorage.is(), "Storage should be open!" ); + if ( pElement->m_bIsFolder && pElement->m_xStorage.is() ) + nCount += pElement->m_xStorage->GetObjectCount(); + } + + return nCount; +} + +static OUString Find_Impl( const Sequence < Sequence < PropertyValue > >& rSequence, std::u16string_view rPath ) +{ + bool bFound = false; + for ( const Sequence < PropertyValue >& rMyProps : rSequence ) + { + OUString aType; + + for ( const PropertyValue& rAny : rMyProps ) + { + if ( rAny.Name == "FullPath" ) + { + OUString aTmp; + if ( ( rAny.Value >>= aTmp ) && aTmp == rPath ) + bFound = true; + if ( !aType.isEmpty() ) + break; + } + else if ( rAny.Name == "MediaType" ) + { + if ( ( rAny.Value >>= aType ) && !aType.isEmpty() && bFound ) + break; + } + } + + if ( bFound ) + return aType; + } + + return OUString(); +} + +void UCBStorage_Impl::SetProps( const Sequence < Sequence < PropertyValue > >& rSequence, const OUString& rPath ) +{ + OUString aPath( rPath ); + if ( !m_bIsRoot ) + aPath += m_aName; + aPath += "/"; + + m_aContentType = m_aOriginalContentType = Find_Impl( rSequence, aPath ); + + if ( m_bIsRoot ) + // the "FullPath" of a child always starts without '/' + aPath.clear(); + + for (auto& pElement : m_aChildrenList) + { + DBG_ASSERT( !pElement->m_bIsFolder || pElement->m_xStorage.is(), "Storage should be open!" ); + if ( pElement->m_bIsFolder && pElement->m_xStorage.is() ) + pElement->m_xStorage->SetProps( rSequence, aPath ); + else + { + OUString aElementPath = aPath + pElement->m_aName; + pElement->SetContentType( Find_Impl( rSequence, aElementPath ) ); + } + } + + if ( m_aContentType.isEmpty() ) + return; + + // get the clipboard format using the content type + css::datatransfer::DataFlavor aDataFlavor; + aDataFlavor.MimeType = m_aContentType; + m_nFormat = SotExchange::GetFormat( aDataFlavor ); + + // get the ClassId using the clipboard format ( internal table ) + m_aClassId = GetClassId_Impl( m_nFormat ); + + // get human presentable name using the clipboard format + SotExchange::GetFormatDataFlavor( m_nFormat, aDataFlavor ); + m_aUserTypeName = aDataFlavor.HumanPresentableName; +} + +void UCBStorage_Impl::GetProps( sal_Int32& nProps, Sequence < Sequence < PropertyValue > >& rSequence, const OUString& rPath ) +{ + auto pSequence = rSequence.getArray(); + + // first my own properties + // first property is the "FullPath" name + // it's '/' for the root storage and m_aName for each element, followed by a '/' if it's a folder + OUString aPath( rPath ); + if ( !m_bIsRoot ) + aPath += m_aName; + aPath += "/"; + Sequence < PropertyValue > aProps{ comphelper::makePropertyValue("MediaType", m_aContentType), + comphelper::makePropertyValue("FullPath", aPath) }; + pSequence[nProps++] = aProps; + + if ( m_bIsRoot ) + // the "FullPath" of a child always starts without '/' + aPath.clear(); + + // now the properties of my elements + for (auto& pElement : m_aChildrenList) + { + DBG_ASSERT( !pElement->m_bIsFolder || pElement->m_xStorage.is(), "Storage should be open!" ); + if ( pElement->m_bIsFolder && pElement->m_xStorage.is() ) + // storages add there properties by themselves ( see above ) + pElement->m_xStorage->GetProps( nProps, rSequence, aPath ); + else + { + // properties of streams + OUString aElementPath = aPath + pElement->m_aName; + aProps = { comphelper::makePropertyValue("MediaType", pElement->GetContentType()), + comphelper::makePropertyValue("FullPath", aElementPath) }; + pSequence[ nProps++ ] = aProps; + } + } +} + +UCBStorage_Impl::~UCBStorage_Impl() +{ + m_aChildrenList.clear(); + + m_pContent.reset(); + m_pTempFile.reset(); +} + +bool UCBStorage_Impl::Insert( ::ucbhelper::Content *pContent ) +{ + // a new substorage is inserted into a UCBStorage ( given by the parameter pContent ) + // it must be inserted with a title and a type + bool bRet = false; + + try + { + const Sequence< ContentInfo > aInfo = pContent->queryCreatableContentsInfo(); + if ( !aInfo.hasElements() ) + return false; + + for ( const ContentInfo & rCurr : aInfo ) + { + // Simply look for the first KIND_FOLDER... + if ( rCurr.Attributes & ContentInfoAttribute::KIND_FOLDER ) + { + // Make sure the only required bootstrap property is "Title", + const Sequence< Property > & rProps = rCurr.Properties; + if ( rProps.getLength() != 1 ) + continue; + + if ( rProps[ 0 ].Name != "Title" ) + continue; + + Content aNewFolder; + if ( !pContent->insertNewContent( rCurr.Type, { "Title" }, { Any(m_aName) }, aNewFolder ) ) + continue; + + // remove old content, create an "empty" new one and initialize it with the new inserted + m_pContent.reset(new ::ucbhelper::Content( aNewFolder )); + bRet = true; + } + } + } + catch (const CommandAbortedException&) + { + // any command wasn't executed successfully - not specified + SetError( ERRCODE_IO_GENERAL ); + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + } + catch (const Exception&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + } + + return bRet; +} + +sal_Int16 UCBStorage_Impl::Commit() +{ + // send all changes to the package + sal_Int16 nRet = COMMIT_RESULT_NOTHING_TO_DO; + + // there is nothing to do if the storage has been opened readonly or if it was opened in transacted mode and no + // commit command has been sent + if ( ( m_nMode & StreamMode::WRITE ) && ( m_bCommited || m_bDirect ) ) + { + try + { + // all errors will be caught in the "catch" statement outside the loop + for ( size_t i = 0; i < m_aChildrenList.size() && nRet; ++i ) + { + auto& pElement = m_aChildrenList[ i ]; + ::ucbhelper::Content* pContent = pElement->GetContent(); + std::unique_ptr< ::ucbhelper::Content > xDeleteContent; + if ( !pContent && pElement->IsModified() ) + { + // if the element has never been opened, no content has been created until now + OUString aName = m_aURL + "/" + pElement->m_aOriginalName; + pContent = new ::ucbhelper::Content( aName, Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + xDeleteContent.reset(pContent); // delete it later on exit scope + } + + if ( pElement->m_bIsRemoved ) + { + // was it inserted, then removed (so there would be nothing to do!) + if ( !pElement->m_bIsInserted ) + { + // first remove all open stream handles + if (pContent && (!pElement->m_xStream.is() || pElement->m_xStream->Clear())) + { + pContent->executeCommand( "delete", Any( true ) ); + nRet = COMMIT_RESULT_SUCCESS; + } + else + // couldn't release stream because there are external references to it + nRet = COMMIT_RESULT_FAILURE; + } + } + else + { + sal_Int16 nLocalRet = COMMIT_RESULT_NOTHING_TO_DO; + if ( pElement->m_xStorage.is() ) + { + // element is a storage + // do a commit in the following cases: + // - if storage is already inserted, and changed + // - storage is not in a package + // - it's a new storage, try to insert and commit if successful inserted + if ( !pElement->m_bIsInserted || m_bIsLinked || pElement->m_xStorage->Insert( m_pContent.get() ) ) + { + nLocalRet = pElement->m_xStorage->Commit(); + pContent = pElement->GetContent(); + } + } + else if ( pElement->m_xStream.is() ) + { + // element is a stream + nLocalRet = pElement->m_xStream->Commit(); + if ( pElement->m_xStream->m_bIsOLEStorage ) + { + // OLE storage should be stored encrypted, if the storage uses encryption + pElement->m_xStream->m_aContentType = "application/vnd.sun.star.oleobject"; + Any aValue; + aValue <<= true; + pElement->m_xStream->m_pContent->setPropertyValue("Encrypted", aValue ); + } + + pContent = pElement->GetContent(); + } + + if (pContent && pElement->m_aName != pElement->m_aOriginalName) + { + // name ( title ) of the element was changed + nLocalRet = COMMIT_RESULT_SUCCESS; + pContent->setPropertyValue("Title", Any(pElement->m_aName) ); + } + + if (pContent && pElement->IsLoaded() && pElement->GetContentType() != pElement->GetOriginalContentType()) + { + // mediatype of the element was changed + nLocalRet = COMMIT_RESULT_SUCCESS; + pContent->setPropertyValue("MediaType", Any(pElement->GetContentType()) ); + } + + if ( nLocalRet != COMMIT_RESULT_NOTHING_TO_DO ) + nRet = nLocalRet; + } + + if ( nRet == COMMIT_RESULT_FAILURE ) + break; + } + } + catch (const ContentCreationException&) + { + // content could not be created + SetError( ERRCODE_IO_NOTEXISTS ); + return COMMIT_RESULT_FAILURE; + } + catch (const CommandAbortedException&) + { + // any command wasn't executed successfully - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const RuntimeException&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const Exception&) + { + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + + if ( m_bIsRoot && m_pContent ) + { + // the root storage must flush the root package content + if ( nRet == COMMIT_RESULT_SUCCESS ) + { + try + { + // commit the media type to the JAR file + // clipboard format and ClassId will be retrieved from the media type when the file is loaded again + Any aType; + aType <<= m_aContentType; + m_pContent->setPropertyValue("MediaType", aType ); + + if ( m_bIsLinked ) + { + // write a manifest file + // first create a subfolder "META-inf" + Content aNewSubFolder; + bool bRet = ::utl::UCBContentHelper::MakeFolder( *m_pContent, "META-INF", aNewSubFolder ); + if ( bRet ) + { + // create a stream to write the manifest file - use a temp file + OUString aURL( aNewSubFolder.getURL() ); + std::optional< ::utl::TempFile> pTempFile(&aURL ); + + // get the stream from the temp file and create an output stream wrapper + SvStream* pStream = pTempFile->GetStream( StreamMode::STD_READWRITE ); + rtl::Reference<::utl::OOutputStreamWrapper> xOutputStream = new ::utl::OOutputStreamWrapper( *pStream ); + + // create a manifest writer object that will fill the stream + Reference < css::packages::manifest::XManifestWriter > xWriter = + css::packages::manifest::ManifestWriter::create( + ::comphelper::getProcessComponentContext() ); + sal_Int32 nCount = GetObjectCount() + 1; + Sequence < Sequence < PropertyValue > > aProps( nCount ); + sal_Int32 nProps = 0; + GetProps( nProps, aProps, OUString() ); + xWriter->writeManifestSequence( xOutputStream, aProps ); + + // move the stream to its desired location + Content aSource( pTempFile->GetURL(), Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + xWriter = nullptr; + xOutputStream = nullptr; + pTempFile.reset(); + aNewSubFolder.transferContent( aSource, InsertOperation::Move, "manifest.xml", NameClash::OVERWRITE ); + } + } + else + { +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("sot", "Files: " << nOpenFiles); + SAL_INFO("sot", "Streams: " << nOpenStreams); +#endif + // force writing + Any aAny; + m_pContent->executeCommand( "flush", aAny ); + if ( m_pSource != nullptr ) + { + std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( m_pTempFile->GetURL(), StreamMode::STD_READ )); + m_pSource->SetStreamSize(0); + // m_pSource->Seek(0); + pStream->ReadStream( *m_pSource ); + pStream.reset(); + m_pSource->Seek(0); + } + } + } + catch (const CommandAbortedException&) + { + // how to tell the content : forget all changes ?! + // or should we assume that the content does it by itself because he threw an exception ?! + // any command wasn't executed successfully - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const RuntimeException&) + { + // how to tell the content : forget all changes ?! + // or should we assume that the content does it by itself because he threw an exception ?! + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + catch (const InteractiveIOException& r) + { + if ( r.Code == IOErrorCode_ACCESS_DENIED || r.Code == IOErrorCode_LOCKING_VIOLATION ) + SetError( ERRCODE_IO_ACCESSDENIED ); + else if ( r.Code == IOErrorCode_NOT_EXISTING ) + SetError( ERRCODE_IO_NOTEXISTS ); + else if ( r.Code == IOErrorCode_CANT_READ ) + SetError( ERRCODE_IO_CANTREAD ); + else if ( r.Code == IOErrorCode_CANT_WRITE ) + SetError( ERRCODE_IO_CANTWRITE ); + else + SetError( ERRCODE_IO_GENERAL ); + + return COMMIT_RESULT_FAILURE; + } + catch (const Exception&) + { + // how to tell the content : forget all changes ?! + // or should we assume that the content does it by itself because he threw an exception ?! + // any other error - not specified + SetError( ERRCODE_IO_GENERAL ); + return COMMIT_RESULT_FAILURE; + } + } + else if ( nRet != COMMIT_RESULT_NOTHING_TO_DO ) + { + // how to tell the content : forget all changes ?! Should we ?! + SetError( ERRCODE_IO_GENERAL ); + return nRet; + } + + // after successful root commit all elements names and types are adjusted and all removed elements + // are also removed from the lists + for ( size_t i = 0; i < m_aChildrenList.size(); ) + { + auto& pInnerElement = m_aChildrenList[ i ]; + if ( pInnerElement->m_bIsRemoved ) + m_aChildrenList.erase( m_aChildrenList.begin() + i ); + else + { + pInnerElement->m_aOriginalName = pInnerElement->m_aName; + pInnerElement->m_bIsInserted = false; + ++i; + } + } + } + + m_bCommited = false; + } + + return nRet; +} + +void UCBStorage_Impl::Revert() +{ + for ( size_t i = 0; i < m_aChildrenList.size(); ) + { + auto& pElement = m_aChildrenList[ i ]; + pElement->m_bIsRemoved = false; + if ( pElement->m_bIsInserted ) + m_aChildrenList.erase( m_aChildrenList.begin() + i ); + else + { + if ( pElement->m_xStream.is() ) + { + pElement->m_xStream->m_bCommited = false; + pElement->m_xStream->Revert(); + } + else if ( pElement->m_xStorage.is() ) + { + pElement->m_xStorage->m_bCommited = false; + pElement->m_xStorage->Revert(); + } + + pElement->m_aName = pElement->m_aOriginalName; + pElement->m_bIsRemoved = false; + ++i; + } + } +} + +const OUString& UCBStorage::GetName() const +{ + return pImp->m_aName; // pImp->m_aURL ?! +} + +bool UCBStorage::IsRoot() const +{ + return pImp->m_bIsRoot; +} + +void UCBStorage::SetDirty() +{ +} + +void UCBStorage::SetClass( const SvGlobalName & rClass, SotClipboardFormatId nOriginalClipFormat, const OUString & rUserTypeName ) +{ + pImp->m_aClassId = rClass; + pImp->m_nFormat = nOriginalClipFormat; + pImp->m_aUserTypeName = rUserTypeName; + + // in UCB storages only the content type will be stored, all other information can be reconstructed + // ( see the UCBStorage_Impl::Init() method ) + css::datatransfer::DataFlavor aDataFlavor; + SotExchange::GetFormatDataFlavor( pImp->m_nFormat, aDataFlavor ); + pImp->m_aContentType = aDataFlavor.MimeType; +} + +void UCBStorage::SetClassId( const ClsId& rClsId ) +{ + pImp->m_aClassId = SvGlobalName( rClsId ); + if ( pImp->m_aClassId == SvGlobalName() ) + return; + + // in OLE storages the clipboard format and the user name will be transferred when a storage is copied because both are + // stored in one the substreams + // UCB storages store the content type information as content type in the manifest file and so this information must be + // kept up to date, and also the other type information that is hold only at runtime because it can be reconstructed from + // the content type + pImp->m_nFormat = GetFormatId_Impl( pImp->m_aClassId ); + if ( pImp->m_nFormat != SotClipboardFormatId::NONE ) + { + css::datatransfer::DataFlavor aDataFlavor; + SotExchange::GetFormatDataFlavor( pImp->m_nFormat, aDataFlavor ); + pImp->m_aUserTypeName = aDataFlavor.HumanPresentableName; + pImp->m_aContentType = aDataFlavor.MimeType; + } +} + +const ClsId& UCBStorage::GetClassId() const +{ + return pImp->m_aClassId.GetCLSID(); +} + +SvGlobalName UCBStorage::GetClassName() +{ + return pImp->m_aClassId; +} + +SotClipboardFormatId UCBStorage::GetFormat() +{ + return pImp->m_nFormat; +} + +OUString UCBStorage::GetUserName() +{ + OSL_FAIL("UserName is not implemented in UCB storages!" ); + return pImp->m_aUserTypeName; +} + +void UCBStorage::FillInfoList( SvStorageInfoList* pList ) const +{ + // put information in childrenlist into StorageInfoList + for (auto& pElement : pImp->GetChildrenList()) + { + if ( !pElement->m_bIsRemoved ) + { + // problem: what about the size of a substorage ?! + sal_uInt64 nSize = pElement->m_nSize; + if ( pElement->m_xStream.is() ) + nSize = pElement->m_xStream->GetSize(); + SvStorageInfo aInfo( pElement->m_aName, nSize, pElement->m_bIsStorage ); + pList->push_back( aInfo ); + } + } +} + +bool UCBStorage::CopyStorageElement_Impl( UCBStorageElement_Impl const & rElement, BaseStorage* pDest, const OUString& rNew ) const +{ + // insert stream or storage into the list or stream of the destination storage + // not into the content, this will be done on commit ! + // be aware of name changes ! + if ( !rElement.m_bIsStorage ) + { + // copy the streams data + // the destination stream must not be open + tools::SvRef<BaseStorageStream> pOtherStream(pDest->OpenStream( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pImp->m_bDirect )); + BaseStorageStream* pStream = nullptr; + bool bDeleteStream = false; + + // if stream is already open, it is allowed to copy it, so be aware of this + if ( rElement.m_xStream.is() ) + pStream = rElement.m_xStream->m_pAntiImpl; + if ( !pStream ) + { + pStream = const_cast< UCBStorage* >(this)->OpenStream( rElement.m_aName, StreamMode::STD_READ, pImp->m_bDirect ); + bDeleteStream = true; + } + + pStream->CopyTo( pOtherStream.get() ); + SetError( pStream->GetError() ); + if( pOtherStream->GetError() ) + pDest->SetError( pOtherStream->GetError() ); + else + pOtherStream->Commit(); + + if ( bDeleteStream ) + delete pStream; + } + else + { + // copy the storage content + // the destination storage must not be open + BaseStorage* pStorage = nullptr; + + // if stream is already open, it is allowed to copy it, so be aware of this + bool bDeleteStorage = false; + if ( rElement.m_xStorage.is() ) + pStorage = rElement.m_xStorage->m_pAntiImpl; + if ( !pStorage ) + { + pStorage = const_cast<UCBStorage*>(this)->OpenStorage( rElement.m_aName, pImp->m_nMode, pImp->m_bDirect ); + bDeleteStorage = true; + } + + UCBStorage* pUCBDest = dynamic_cast<UCBStorage*>( pDest ); + UCBStorage* pUCBCopy = dynamic_cast<UCBStorage*>( pStorage ); + + bool bOpenUCBStorage = pUCBDest && pUCBCopy; + tools::SvRef<BaseStorage> pOtherStorage(bOpenUCBStorage ? + pDest->OpenUCBStorage( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pImp->m_bDirect ) : + pDest->OpenOLEStorage( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pImp->m_bDirect )); + + // For UCB storages, the class id and the format id may differ, + // do passing the class id is not sufficient. + if( bOpenUCBStorage ) + pOtherStorage->SetClass( pStorage->GetClassName(), + pStorage->GetFormat(), + pUCBCopy->pImp->m_aUserTypeName ); + else + pOtherStorage->SetClassId( pStorage->GetClassId() ); + pStorage->CopyTo( pOtherStorage.get() ); + SetError( pStorage->GetError() ); + if( pOtherStorage->GetError() ) + pDest->SetError( pOtherStorage->GetError() ); + else + pOtherStorage->Commit(); + + if ( bDeleteStorage ) + delete pStorage; + } + + return Good() && pDest->Good(); +} + +UCBStorageElement_Impl* UCBStorage::FindElement_Impl( std::u16string_view rName ) const +{ + DBG_ASSERT( !rName.empty(), "Name is empty!" ); + for (const auto& pElement : pImp->GetChildrenList()) + { + if ( pElement->m_aName == rName && !pElement->m_bIsRemoved ) + return pElement.get(); + } + return nullptr; +} + +bool UCBStorage::CopyTo( BaseStorage* pDestStg ) const +{ + DBG_ASSERT( pDestStg != static_cast<BaseStorage const *>(this), "Self-Copying is not possible!" ); + if ( pDestStg == static_cast<BaseStorage const *>(this) ) + return false; + + // perhaps it's also a problem if one storage is a parent of the other ?! + // or if not: could be optimized ?! + + // For UCB storages, the class id and the format id may differ, + // do passing the class id is not sufficient. + if( dynamic_cast<const UCBStorage *>(pDestStg) != nullptr ) + pDestStg->SetClass( pImp->m_aClassId, pImp->m_nFormat, + pImp->m_aUserTypeName ); + else + pDestStg->SetClassId( GetClassId() ); + pDestStg->SetDirty(); + + bool bRet = true; + for ( size_t i = 0; i < pImp->GetChildrenList().size() && bRet; ++i ) + { + auto& pElement = pImp->GetChildrenList()[ i ]; + if ( !pElement->m_bIsRemoved ) + bRet = CopyStorageElement_Impl( *pElement, pDestStg, pElement->m_aName ); + } + + if( !bRet ) + SetError( pDestStg->GetError() ); + return Good() && pDestStg->Good(); +} + +bool UCBStorage::CopyTo( const OUString& rElemName, BaseStorage* pDest, const OUString& rNew ) +{ + if( rElemName.isEmpty() ) + return false; + + if ( pDest == static_cast<BaseStorage*>(this) ) + { + // can't double an element + return false; + } + else + { + // for copying no optimization is useful, because in every case the stream data must be copied + UCBStorageElement_Impl* pElement = FindElement_Impl( rElemName ); + if ( pElement ) + return CopyStorageElement_Impl( *pElement, pDest, rNew ); + else + { + SetError( SVSTREAM_FILE_NOT_FOUND ); + return false; + } + } +} + +bool UCBStorage::Commit() +{ + // mark this storage for sending it on root commit + pImp->m_bCommited = true; + if ( pImp->m_bIsRoot ) + // the root storage coordinates committing by sending a Commit command to its content + return ( pImp->Commit() != COMMIT_RESULT_FAILURE ); + else + return true; +} + +bool UCBStorage::Revert() +{ + pImp->Revert(); + return true; +} + +BaseStorageStream* UCBStorage::OpenStream( const OUString& rEleName, StreamMode nMode, bool bDirect ) +{ + if( rEleName.isEmpty() ) + return nullptr; + + // try to find the storage element + UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + if ( !pElement ) + { + // element does not exist, check if creation is allowed + if( nMode & StreamMode::NOCREATE ) + { + SetError( ( nMode & StreamMode::WRITE ) ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + OUString aName = pImp->m_aURL + "/" + rEleName; + UCBStorageStream* pStream = new UCBStorageStream( aName, nMode, bDirect, pImp->m_bRepairPackage, pImp->m_xProgressHandler ); + pStream->SetError( GetError() ); + pStream->pImp->m_aName = rEleName; + return pStream; + } + else + { + // create a new UCBStorageElement and insert it into the list + pElement = new UCBStorageElement_Impl( rEleName ); + pElement->m_bIsInserted = true; + pImp->m_aChildrenList.emplace_back( pElement ); + } + } + + if ( !pElement->m_bIsFolder ) + { + // check if stream is already created + if ( pElement->m_xStream.is() ) + { + // stream has already been created; if it has no external reference, it may be opened another time + if ( pElement->m_xStream->m_pAntiImpl ) + { + OSL_FAIL("Stream is already open!" ); + SetError( SVSTREAM_ACCESS_DENIED ); // ??? + return nullptr; + } + else + { + // check if stream is opened with the same keyword as before + // if not, generate a new stream because it could be encrypted vs. decrypted! + if ( pElement->m_xStream->m_aKey.isEmpty() ) + { + pElement->m_xStream->PrepareCachedForReopen( nMode ); + + return new UCBStorageStream( pElement->m_xStream.get() ); + } + } + } + + // stream is opened the first time + pImp->OpenStream( pElement, nMode, bDirect ); + + // if name has been changed before creating the stream: set name! + pElement->m_xStream->m_aName = rEleName; + return new UCBStorageStream( pElement->m_xStream.get() ); + } + + return nullptr; +} + +void UCBStorage_Impl::OpenStream( UCBStorageElement_Impl* pElement, StreamMode nMode, bool bDirect ) +{ + OUString aName = m_aURL + "/" +pElement->m_aOriginalName; + pElement->m_xStream = new UCBStorageStream_Impl( aName, nMode, nullptr, bDirect, m_bRepairPackage, m_xProgressHandler ); +} + +BaseStorage* UCBStorage::OpenUCBStorage( const OUString& rEleName, StreamMode nMode, bool bDirect ) +{ + if( rEleName.isEmpty() ) + return nullptr; + + return OpenStorage_Impl( rEleName, nMode, bDirect, true ); +} + +BaseStorage* UCBStorage::OpenOLEStorage( const OUString& rEleName, StreamMode nMode, bool bDirect ) +{ + if( rEleName.isEmpty() ) + return nullptr; + + return OpenStorage_Impl( rEleName, nMode, bDirect, false ); +} + +BaseStorage* UCBStorage::OpenStorage( const OUString& rEleName, StreamMode nMode, bool bDirect ) +{ + if( rEleName.isEmpty() ) + return nullptr; + + return OpenStorage_Impl( rEleName, nMode, bDirect, true ); +} + +BaseStorage* UCBStorage::OpenStorage_Impl( const OUString& rEleName, StreamMode nMode, bool bDirect, bool bForceUCBStorage ) +{ + // try to find the storage element + UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + if ( !pElement ) + { + // element does not exist, check if creation is allowed + if( nMode & StreamMode::NOCREATE ) + { + SetError( ( nMode & StreamMode::WRITE ) ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + OUString aName = pImp->m_aURL + "/" + rEleName; // ??? + UCBStorage *pStorage = new UCBStorage( aName, nMode, bDirect, false, pImp->m_bRepairPackage, pImp->m_xProgressHandler ); + pStorage->pImp->m_bIsRoot = false; + pStorage->pImp->m_bListCreated = true; // the storage is pretty new, nothing to read + pStorage->SetError( GetError() ); + return pStorage; + } + + // create a new UCBStorageElement and insert it into the list + // problem: perhaps an OLEStorage should be created ?! + // Because nothing is known about the element that should be created, an external parameter is needed ! + pElement = new UCBStorageElement_Impl( rEleName ); + pElement->m_bIsInserted = true; + pImp->m_aChildrenList.emplace_back( pElement ); + } + + if ( !pElement->m_bIsFolder && ( pElement->m_bIsStorage || !bForceUCBStorage ) ) + { + // create OLE storages on a stream ( see ctor of SotStorage ) + // Such a storage will be created on a UCBStorageStream; it will write into the stream + // if it is opened in direct mode or when it is committed. In this case the stream will be + // modified and then it MUST be treated as committed. + if ( !pElement->m_xStream.is() ) + { + BaseStorageStream* pStr = OpenStream( rEleName, nMode, bDirect ); + UCBStorageStream* pStream = dynamic_cast<UCBStorageStream*>( pStr ); + if ( !pStream ) + { + SetError( ( nMode & StreamMode::WRITE ) ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + return nullptr; + } + + pElement->m_xStream = pStream->pImp; + delete pStream; + } + + pElement->m_xStream->PrepareCachedForReopen( nMode ); + bool bInited = pElement->m_xStream->Init(); + if (!bInited) + { + SetError( ( nMode & StreamMode::WRITE ) ? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND ); + return nullptr; + } + + pElement->m_bIsStorage = true; + return pElement->m_xStream->CreateStorage(); // can only be created in transacted mode + } + else if ( pElement->m_xStorage.is() ) + { + // storage has already been opened; if it has no external reference, it may be opened another time + if ( pElement->m_xStorage->m_pAntiImpl ) + { + OSL_FAIL("Storage is already open!" ); + SetError( SVSTREAM_ACCESS_DENIED ); // ??? + } + else + { + bool bIsWritable = bool( pElement->m_xStorage->m_nMode & StreamMode::WRITE ); + if ( !bIsWritable && ( nMode & StreamMode::WRITE ) ) + { + OUString aName = pImp->m_aURL + "/" + pElement->m_aOriginalName; + UCBStorage* pStorage = new UCBStorage( aName, nMode, bDirect, false, pImp->m_bRepairPackage, pImp->m_xProgressHandler ); + pElement->m_xStorage = pStorage->pImp; + return pStorage; + } + else + { + return new UCBStorage( pElement->m_xStorage.get() ); + } + } + } + else if ( !pElement->m_xStream.is() ) + { + // storage is opened the first time + bool bIsWritable = bool(pImp->m_nMode & StreamMode::WRITE); + if ( pImp->m_bIsLinked && pImp->m_bIsRoot && bIsWritable ) + { + // make sure that the root storage object has been created before substorages will be created + INetURLObject aFolderObj( pImp->m_aURL ); + aFolderObj.removeSegment(); + + Content aFolder( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + pImp->m_pContent.reset(new Content); + bool bRet = ::utl::UCBContentHelper::MakeFolder( aFolder, pImp->m_aName, *pImp->m_pContent ); + if ( !bRet ) + { + SetError( SVSTREAM_CANNOT_MAKE ); + return nullptr; + } + } + + UCBStorage_Impl* pStor = pImp->OpenStorage( pElement, nMode, bDirect ); + if ( pStor ) + { + if ( pElement->m_bIsInserted ) + pStor->m_bListCreated = true; // the storage is pretty new, nothing to read + + return new UCBStorage( pStor ); + } + } + + return nullptr; +} + +UCBStorage_Impl* UCBStorage_Impl::OpenStorage( UCBStorageElement_Impl* pElement, StreamMode nMode, bool bDirect ) +{ + UCBStorage_Impl* pRet = nullptr; + OUString aName = m_aURL + "/" + pElement->m_aOriginalName; // ??? + + pElement->m_bIsStorage = pElement->m_bIsFolder = true; + + if ( m_bIsLinked && !::utl::UCBContentHelper::Exists( aName ) ) + { + Content aNewFolder; + bool bRet = ::utl::UCBContentHelper::MakeFolder( *m_pContent, pElement->m_aOriginalName, aNewFolder ); + if ( bRet ) + pRet = new UCBStorage_Impl( aNewFolder, aName, nMode, nullptr, bDirect, false, m_bRepairPackage, m_xProgressHandler ); + } + else + { + pRet = new UCBStorage_Impl( aName, nMode, nullptr, bDirect, false, m_bRepairPackage, m_xProgressHandler ); + } + + if ( pRet ) + { + pRet->m_bIsLinked = m_bIsLinked; + pRet->m_bIsRoot = false; + + // if name has been changed before creating the stream: set name! + pRet->m_aName = pElement->m_aOriginalName; + pElement->m_xStorage = pRet; + } + + if ( pRet ) + pRet->Init(); + + return pRet; +} + +bool UCBStorage::IsStorage( const OUString& rEleName ) const +{ + if( rEleName.isEmpty() ) + return false; + + const UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + return ( pElement && pElement->m_bIsStorage ); +} + +bool UCBStorage::IsStream( const OUString& rEleName ) const +{ + if( rEleName.isEmpty() ) + return false; + + const UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + return ( pElement && !pElement->m_bIsStorage ); +} + +bool UCBStorage::IsContained( const OUString & rEleName ) const +{ + if( rEleName.isEmpty() ) + return false; + const UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + return ( pElement != nullptr ); +} + +void UCBStorage::Remove( const OUString& rEleName ) +{ + if( rEleName.isEmpty() ) + return; + + UCBStorageElement_Impl *pElement = FindElement_Impl( rEleName ); + if ( pElement ) + { + pElement->m_bIsRemoved = true; + } + else + SetError( SVSTREAM_FILE_NOT_FOUND ); +} + +bool UCBStorage::ValidateFAT() +{ + // ??? + return true; +} + +bool UCBStorage::Validate( bool bWrite ) const +{ + // ??? + return ( !bWrite || ( pImp->m_nMode & StreamMode::WRITE ) ); +} + +bool UCBStorage::ValidateMode( StreamMode m ) const +{ + // ??? + if( m == ( StreamMode::READ | StreamMode::TRUNC ) ) // from stg.cxx + return true; + // only SHARE_DENYALL allowed + // storages open in r/o mode are OK, since only + // the commit may fail + if( m & StreamMode::SHARE_DENYALL ) + return true; + + return true; +} + +bool UCBStorage::Equals( const BaseStorage& rStorage ) const +{ + // ??? + return static_cast<BaseStorage const *>(this) == &rStorage; +} + +bool UCBStorage::IsStorageFile( SvStream* pFile ) +{ + if ( !pFile ) + return false; + + sal_uInt64 nPos = pFile->Tell(); + if ( pFile->TellEnd() < 4 ) + return false; + + pFile->Seek(0); + sal_uInt32 nBytes(0); + pFile->ReadUInt32( nBytes ); + + // search for the magic bytes + bool bRet = ( nBytes == 0x04034b50 ); + if ( !bRet ) + { + // disk spanned file have an additional header in front of the usual one + bRet = ( nBytes == 0x08074b50 ); + if ( bRet ) + { + nBytes = 0; + pFile->ReadUInt32( nBytes ); + bRet = ( nBytes == 0x04034b50 ); + } + } + + pFile->Seek( nPos ); + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |