summaryrefslogtreecommitdiffstats
path: root/sot/source/sdstor
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sot/source/sdstor
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sot/source/sdstor')
-rw-r--r--sot/source/sdstor/stg.cxx940
-rw-r--r--sot/source/sdstor/stgavl.cxx411
-rw-r--r--sot/source/sdstor/stgavl.hxx67
-rw-r--r--sot/source/sdstor/stgcache.cxx430
-rw-r--r--sot/source/sdstor/stgcache.hxx131
-rw-r--r--sot/source/sdstor/stgdir.cxx938
-rw-r--r--sot/source/sdstor/stgdir.hxx111
-rw-r--r--sot/source/sdstor/stgelem.cxx484
-rw-r--r--sot/source/sdstor/stgelem.hxx146
-rw-r--r--sot/source/sdstor/stgio.cxx400
-rw-r--r--sot/source/sdstor/stgio.hxx63
-rw-r--r--sot/source/sdstor/stgole.cxx180
-rw-r--r--sot/source/sdstor/stgole.hxx67
-rw-r--r--sot/source/sdstor/stgstrms.cxx1356
-rw-r--r--sot/source/sdstor/stgstrms.hxx168
-rw-r--r--sot/source/sdstor/storage.cxx803
-rw-r--r--sot/source/sdstor/storinfo.cxx98
-rw-r--r--sot/source/sdstor/ucbstorage.cxx2846
18 files changed, 9639 insertions, 0 deletions
diff --git a/sot/source/sdstor/stg.cxx b/sot/source/sdstor/stg.cxx
new file mode 100644
index 0000000000..682aa08f50
--- /dev/null
+++ b/sot/source/sdstor/stg.cxx
@@ -0,0 +1,940 @@
+/* -*- 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::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 );
+ // coverity[overwrite_var] - ownership is not here, but with StgDirStrm
+ 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 0000000000..98a86f3edb
--- /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 0000000000..c78df17229
--- /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 0000000000..93f7d3090c
--- /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 0000000000..7efbffae5b
--- /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 0000000000..69ca094f37
--- /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 0000000000..3605c27ac0
--- /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 0000000000..ff41d8d70f
--- /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 0000000000..855b2b8795
--- /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 0000000000..604d082828
--- /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 0000000000..7ed5917ac6
--- /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 0000000000..ac23f7a145
--- /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 0000000000..899b07fcd9
--- /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 0000000000..61682ead8a
--- /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::CreateTempURL();
+ 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_uInt64 nCur = Tell();
+ sal_uInt64 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 0000000000..51c08faf53
--- /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 0000000000..eff6a4fdf6
--- /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 <comphelper/diagnose_ex.hxx>
+#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 0000000000..4e42ea315c
--- /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 <comphelper/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 0000000000..fbfafe9727
--- /dev/null
+++ b/sot/source/sdstor/ucbstorage.cxx
@@ -0,0 +1,2846 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#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(), getXWeak());
+
+ 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(), getXWeak());
+
+ 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_uInt64 nPos = m_pSvStream->Tell();
+ checkError();
+ return 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(), getXWeak());
+ 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(), getXWeak());
+}
+
+
+#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::optional<::ucbhelper::Content> m_oContent; // the content that provides the storage elements
+ std::unique_ptr<::utl::TempFileNamed> 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_oContent )
+ CreateContent();
+ return m_oContent ? &*m_oContent : nullptr;
+ }
+ 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::CreateTempURL();
+
+ 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 byte 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_oContent( 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::TempFileNamed);
+ 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::TempFileNamed);
+ 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::TempFileNamed )
+ , 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_oContent )
+ CreateContent();
+
+ if ( m_oContent )
+ {
+ 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_oContent->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_oContent && !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_oContent.emplace( 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_oContent )
+ return;
+
+ // create cursor for access to children
+ Reference< XResultSet > xResultSet = m_oContent->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_oContent.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_oContent.emplace( 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_oContent ? &*m_oContent : nullptr ) )
+ {
+ 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_oContent )
+ {
+ // 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_oContent->setPropertyValue("MediaType", aType );
+
+ if ( m_bIsLinked )
+ {
+ // write a manifest file
+ // first create a subfolder "META-inf"
+ Content aNewSubFolder;
+ bool bRet = ::utl::UCBContentHelper::MakeFolder( *m_oContent, "META-INF", aNewSubFolder );
+ if ( bRet )
+ {
+ // create a stream to write the manifest file - use a temp file
+ OUString aURL( aNewSubFolder.getURL() );
+ std::optional< ::utl::TempFileNamed > 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_oContent->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_oContent.emplace();
+ bool bRet = ::utl::UCBContentHelper::MakeFolder( aFolder, pImp->m_aName, *pImp->m_oContent );
+ 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_oContent, 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: */