/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include SbiImage::SbiImage() : bError(false) , nFlags(SbiImageFlags::NONE) , nStringSize(0) , nDimBase(0) , eCharSet(osl_getThreadTextEncoding()) , nStringIdx(0) , nStringOff(0) , bInit(false) , bFirstInit(true) { } SbiImage::~SbiImage() { } void SbiImage::Clear() { mvStringOffsets.clear(); pStrings.reset(); aCode.clear(); aLegacyPCode.clear(); nFlags = SbiImageFlags::NONE; nStringSize= 0; eCharSet = osl_getThreadTextEncoding(); nDimBase = 0; bError = false; } // Open Record static sal_uInt64 SbiOpenRecord( SvStream& r, FileOffset nSignature, sal_uInt16 nElem ) { sal_uInt64 nPos = r.Tell(); r.WriteUInt16( static_cast( nSignature ) ) .WriteInt32( 0 ).WriteUInt16( nElem ); return nPos; } // Close Record static void SbiCloseRecord( SvStream& r, sal_uInt64 nOff ) { sal_uInt64 nPos = r.Tell(); r.Seek( nOff + 2 ); r.WriteInt32(nPos - nOff - 8 ); r.Seek( nPos ); } constexpr sal_uInt32 nUnicodeDataMagicNumber = 0x556E6920; // "Uni " BE static bool GetToUnicodePoolData(SvStream& r, sal_uInt64 nLen, sal_uInt64 nNext) { const auto nPos = r.Tell(); // Check space for legacy data, magic number and Unicode data bool bResult = nPos + nLen + sizeof(sal_uInt32) + nLen * sizeof(sal_Unicode) <= nNext; if (bResult) { r.SeekRel(nLen); // Skip legacy data sal_uInt32 nMagic = 0; r.ReadUInt32(nMagic); if (nMagic != nUnicodeDataMagicNumber) { r.Seek(nPos); // return bResult = false; } } return bResult; } bool SbiImage::Load( SvStream& r, sal_uInt32& nVersion ) { sal_uInt16 nSign, nCount; sal_uInt32 nLen; Clear(); // Read Master-Record r.ReadUInt16( nSign ).ReadUInt32( nLen ).ReadUInt16( nCount ); sal_uInt64 nLast = r.Tell() + nLen; bool bBadVer = false; if( nSign == static_cast( FileOffset::Module ) ) { sal_uInt32 nCharSet; // System charset sal_uInt32 lDimBase; sal_uInt16 nTmpFlags; sal_uInt16 nReserved1; sal_uInt32 nReserved2; sal_uInt32 nReserved3; r.ReadUInt32( nVersion ).ReadUInt32( nCharSet ).ReadUInt32( lDimBase ) .ReadUInt16( nTmpFlags ).ReadUInt16( nReserved1 ).ReadUInt32( nReserved2 ).ReadUInt32( nReserved3 ); nFlags = static_cast(nTmpFlags); eCharSet = nCharSet; eCharSet = GetSOLoadTextEncoding( eCharSet ); bBadVer = ( nVersion > B_CURVERSION ); nDimBase = static_cast(lDimBase); } bool bLegacy = ( nVersion < B_EXT_IMG_VERSION ); sal_uInt64 nNext; while( ( nNext = r.Tell() ) < nLast ) { r.ReadUInt16( nSign ).ReadUInt32( nLen ).ReadUInt16( nCount ); nNext += nLen + 8; if( r.GetError() == ERRCODE_NONE ) { switch( static_cast( nSign ) ) { case FileOffset::Name: aName = r.ReadUniOrByteString(eCharSet); break; case FileOffset::Comment: aComment = r.ReadUniOrByteString(eCharSet ); break; case FileOffset::Source: { aOUSource = r.ReadUniOrByteString(eCharSet); break; } case FileOffset::ExtSource: { //assuming an empty string with just the lead 32bit/16bit len indicator const size_t nMinStringSize = (eCharSet == RTL_TEXTENCODING_UNICODE) ? 4 : 2; const sal_uInt64 nMaxStrings = r.remainingSize() / nMinStringSize; if (nCount > nMaxStrings) { SAL_WARN("basic", "Parsing error: " << nMaxStrings << " max possible entries, but " << nCount << " claimed, truncating"); nCount = nMaxStrings; } for( sal_uInt16 j = 0; j < nCount; ++j) { aOUSource += r.ReadUniOrByteString(eCharSet); } break; } case FileOffset::PCode: if( bBadVer ) break; aCode.resize(nLen); r.ReadBytes(aCode.data(), aCode.size()); if ( bLegacy ) { aLegacyPCode = std::move(aCode); PCodeBuffConvertor aLegacyToNew(aLegacyPCode.data(), aLegacyPCode.size()); aLegacyToNew.convert(); aCode = aLegacyToNew.GetBuffer(); // we don't release the legacy buffer // right now, that's because the module // needs it to fix up the method // nStart members. When that is done // the module can release the buffer // or it can wait until this routine // is called again or when this class // destructs all of which will trigger // release of the buffer. } break; case FileOffset::Publics: case FileOffset::PoolDir: case FileOffset::SymPool: case FileOffset::LineRanges: break; case FileOffset::StringPool: { // the data layout is: nCount of 32-bit offsets into both legacy 1-byte char stream // and resulting char buffer (1:1 correspondence assumed; 16 of 32 bits used); // 32-bit length N of following 1-byte char stream (16 bits used); N bytes of 1-byte // char stream; then optional magic number and stream of N sal_Unicode characters. if( bBadVer ) break; //assuming an empty string with just the lead 32bit len indicator const sal_uInt64 nMinStringSize = 4; const sal_uInt64 nMaxStrings = r.remainingSize() / nMinStringSize; if (nCount > nMaxStrings) { SAL_WARN("basic", "Parsing error: " << nMaxStrings << " max possible entries, but " << nCount << " claimed, truncating"); nCount = nMaxStrings; } MakeStrings( nCount ); for (size_t i = 0; i < mvStringOffsets.size() && r.good(); i++) { sal_uInt32 nOff; r.ReadUInt32( nOff ); mvStringOffsets[ i ] = static_cast(nOff); } r.ReadUInt32( nLen ); if (r.good()) { pStrings.reset(new sal_Unicode[ nLen ]); nStringSize = static_cast(nLen); if (GetToUnicodePoolData(r, nLen, nNext)) { OUString s = read_uInt16s_ToOUString(r, nLen); memcpy(pStrings.get(), s.getStr(), s.getLength() * sizeof(sal_Unicode)); } else { std::unique_ptr pByteStrings(new char[nLen]); r.ReadBytes(pByteStrings.get(), nLen); for (size_t j = 0; j < mvStringOffsets.size(); j++) { sal_uInt16 nOff2 = static_cast(mvStringOffsets[j]); OUString aStr(pByteStrings.get() + nOff2, strlen(pByteStrings.get() + nOff2), eCharSet); memcpy(pStrings.get() + nOff2, aStr.getStr(), (aStr.getLength() + 1) * sizeof(sal_Unicode)); } } } break; } case FileOffset::UserTypes: { //assuming an empty string with just the lead 32bit/16bit len indicator const size_t nMinStringSize = (eCharSet == RTL_TEXTENCODING_UNICODE) ? 4 : 2; const sal_uInt64 nMinRecordSize = nMinStringSize + sizeof(sal_Int16); const sal_uInt64 nMaxRecords = r.remainingSize() / nMinRecordSize; if (nCount > nMaxRecords) { SAL_WARN("basic", "Parsing error: " << nMaxRecords << " max possible entries, but " << nCount << " claimed, truncating"); nCount = nMaxRecords; } // User defined types; ref.: SbiParser::DefType for (sal_uInt16 i = 0; i < nCount; i++) { OUString aTypeName = r.ReadUniOrByteString(eCharSet); sal_uInt16 nTypeMembers; r.ReadUInt16(nTypeMembers); const sal_uInt64 nMaxTypeMembers = r.remainingSize() / 8; if (nTypeMembers > nMaxTypeMembers) { SAL_WARN("basic", "Parsing error: " << nMaxTypeMembers << " max possible entries, but " << nTypeMembers << " claimed, truncating"); nTypeMembers = nMaxTypeMembers; } SbxObject *pType = new SbxObject(aTypeName); SbxArray *pTypeMembers = pType->GetProperties(); for (sal_uInt16 j = 0; j < nTypeMembers; j++) { OUString aMemberName = r.ReadUniOrByteString(eCharSet); sal_Int16 aIntMemberType; r.ReadInt16(aIntMemberType); SbxDataType aMemberType = static_cast< SbxDataType > ( aIntMemberType ); SbxProperty *pTypeElem = new SbxProperty( aMemberName, aMemberType ); sal_uInt32 aIntFlag; r.ReadUInt32(aIntFlag); SbxFlagBits nElemFlags = static_cast< SbxFlagBits > ( aIntFlag ); pTypeElem->SetFlags(nElemFlags); sal_Int16 hasObject; r.ReadInt16(hasObject); if (hasObject == 1) { if(aMemberType == SbxOBJECT) { // nested user defined types // declared before use, so it is ok to reference it by name on load OUString aNestedTypeName = r.ReadUniOrByteString(eCharSet); SbxObject* pNestedTypeObj = static_cast< SbxObject* >( rTypes->Find( aNestedTypeName, SbxClassType::Object ) ); if (pNestedTypeObj) { SbxObjectRef pCloneObj = cloneTypeObjectImpl( *pNestedTypeObj ); pTypeElem->PutObject( pCloneObj.get() ); } } else { // an array SbxDimArray* pArray = new SbxDimArray( static_cast(aMemberType & 0x0FFF)); sal_Int16 isFixedSize; r.ReadInt16(isFixedSize); if (isFixedSize == 1) pArray->setHasFixedSize( true ); sal_Int32 nDims; r.ReadInt32(nDims); for (sal_Int32 d = 0; d < nDims; d++) { sal_Int32 lBound; sal_Int32 uBound; r.ReadInt32(lBound).ReadInt32(uBound); pArray->unoAddDim(lBound, uBound); } const SbxFlagBits nSavFlags = pTypeElem->GetFlags(); // need to reset the FIXED flag // when calling PutObject ( because the type will not match Object ) pTypeElem->ResetFlag(SbxFlagBits::Fixed); pTypeElem->PutObject( pArray ); pTypeElem->SetFlags(nSavFlags); } } pTypeMembers->Insert(pTypeElem, pTypeMembers->Count()); } pType->Remove( "Name", SbxClassType::DontCare ); pType->Remove( "Parent", SbxClassType::DontCare ); AddType(pType); } break; } case FileOffset::ModEnd: goto done; default: break; } } else { break; } r.Seek( nNext ); } done: r.Seek( nLast ); if (!r.good()) { bError = true; } return !bError; } bool SbiImage::Save( SvStream& r, sal_uInt32 nVer ) { bool bLegacy = ( nVer < B_EXT_IMG_VERSION ); // detect if old code exceeds legacy limits // if so, then disallow save if ( bLegacy && ExceedsLegacyLimits() ) { SbiImage aEmptyImg; aEmptyImg.aName = aName; aEmptyImg.Save( r, B_LEGACYVERSION ); return true; } // First of all the header sal_uInt64 nStart = SbiOpenRecord( r, FileOffset::Module, 1 ); sal_uInt64 nPos; eCharSet = GetSOStoreTextEncoding( eCharSet ); if ( bLegacy ) { r.WriteInt32( B_LEGACYVERSION ); } else { r.WriteInt32( B_CURVERSION ); } r .WriteInt32( eCharSet ) .WriteInt32( nDimBase ) .WriteInt16( static_cast(nFlags) ) .WriteInt16( 0 ) .WriteInt32( 0 ) .WriteInt32( 0 ); // Name? if (!aName.isEmpty() && r.good()) { nPos = SbiOpenRecord( r, FileOffset::Name, 1 ); r.WriteUniOrByteString( aName, eCharSet ); SbiCloseRecord( r, nPos ); } // Comment? if (!aComment.isEmpty() && r.good()) { nPos = SbiOpenRecord( r, FileOffset::Comment, 1 ); r.WriteUniOrByteString( aComment, eCharSet ); SbiCloseRecord( r, nPos ); } // Source? if (!aOUSource.isEmpty() && r.good()) { nPos = SbiOpenRecord( r, FileOffset::Source, 1 ); r.WriteUniOrByteString( aOUSource, eCharSet ); SbiCloseRecord( r, nPos ); } // Binary data? if (aCode.size() && r.good()) { nPos = SbiOpenRecord( r, FileOffset::PCode, 1 ); if ( bLegacy ) { PCodeBuffConvertor aNewToLegacy(aCode.data(), aCode.size()); aNewToLegacy.convert(); aLegacyPCode = aNewToLegacy.GetBuffer(); r.WriteBytes(aLegacyPCode.data(), aLegacyPCode.size()); } else { r.WriteBytes(aCode.data(), aCode.size()); } SbiCloseRecord( r, nPos ); } // String-Pool? if( !mvStringOffsets.empty() ) { nPos = SbiOpenRecord( r, FileOffset::StringPool, mvStringOffsets.size() ); // For every String: // sal_uInt32 Offset of the Strings in the Stringblock for (size_t i = 0; i < mvStringOffsets.size() && r.good(); i++) { r.WriteUInt32( mvStringOffsets[ i ] ); } // Then the String-Block std::unique_ptr pByteStrings(new char[ nStringSize ]); for( size_t i = 0; i < mvStringOffsets.size(); i++ ) { sal_uInt16 nOff = static_cast(mvStringOffsets[ i ]); OString aStr(OUStringToOString(std::u16string_view(pStrings.get() + nOff), eCharSet)); memcpy( pByteStrings.get() + nOff, aStr.getStr(), (aStr.getLength() + 1) * sizeof( char ) ); } r.WriteUInt32( nStringSize ); r.WriteBytes(pByteStrings.get(), nStringSize); pByteStrings.reset(); // Now write magic number and store the same data in UTF-16; this is backward compatible: // old readers will not read this data after having read legacy data, and will proceed // straight to the end of the record. So no version restriction here. r.WriteUInt32(nUnicodeDataMagicNumber); write_uInt16s_FromOUString(r, std::u16string_view(pStrings.get(), nStringSize)); SbiCloseRecord( r, nPos ); } // User defined types if ( rTypes.is() ) { sal_uInt32 nTypes = rTypes->Count(); assert(nTypes <= std::numeric_limits::max()); if (nTypes > 0 ) { nPos = SbiOpenRecord( r, FileOffset::UserTypes, sal::static_int_cast(nTypes) ); for (sal_uInt32 i = 0; i < nTypes; i++) { SbxObject* pType = static_cast(rTypes->Get(i)); OUString aTypeName = pType->GetClassName(); r.WriteUniOrByteString( aTypeName, eCharSet ); SbxArray *pTypeMembers = pType->GetProperties(); sal_uInt32 nTypeMembers = pTypeMembers->Count(); assert(nTypeMembers <= std::numeric_limits::max()); r.WriteInt16(sal::static_int_cast(nTypeMembers)); for (sal_uInt32 j = 0; j < nTypeMembers; j++) { SbxProperty* pTypeElem = static_cast(pTypeMembers->Get(j)); const OUString& aElemName = pTypeElem->GetName(); r.WriteUniOrByteString( aElemName, eCharSet ); SbxDataType dataType = pTypeElem->GetType(); r.WriteInt16(dataType); SbxFlagBits nElemFlags = pTypeElem->GetFlags(); r.WriteUInt32(static_cast< sal_uInt32 > (nElemFlags) ); SbxBase* pElemObject = pTypeElem->GetObject(); if (pElemObject) { r.WriteInt16(1); // has elem Object if( dataType == SbxOBJECT ) { // nested user defined types // declared before use, so it is ok to reference it by name on load SbxObject* pNestedType = static_cast< SbxObject* > ( pElemObject ); r.WriteUniOrByteString( pNestedType->GetClassName(), eCharSet ); } else { // an array SbxDimArray* pArray = static_cast< SbxDimArray* > ( pElemObject ); bool bFixedSize = pArray->hasFixedSize(); if (bFixedSize) r.WriteInt16(1); else r.WriteInt16(0); sal_Int32 nDims = pArray->GetDims(); r.WriteInt32(nDims); for (sal_Int32 d = 1; d <= nDims; d++) { sal_Int32 lBound; sal_Int32 uBound; pArray->GetDim(d, lBound, uBound); r.WriteInt32(lBound).WriteInt32(uBound); } } } else r.WriteInt16(0); // no elem Object } } SbiCloseRecord( r, nPos ); } } // Set overall length SbiCloseRecord( r, nStart ); if (!r.good()) { bError = true; } return !bError; } void SbiImage::MakeStrings( short nSize ) { nStringIdx = 0; nStringOff = 0; nStringSize = 1024; pStrings.reset( new sal_Unicode[ nStringSize ]); mvStringOffsets.resize(nSize); if (nSize != 0) { memset( mvStringOffsets.data(), 0, nSize * sizeof( sal_uInt32 ) ); } memset( pStrings.get(), 0, nStringSize * sizeof( sal_Unicode ) ); } // Add a string to StringPool. The String buffer is dynamically // growing in 1K-Steps void SbiImage::AddString( const OUString& r ) { if( nStringIdx >= mvStringOffsets.size() ) { bError = true; } if( bError ) return; sal_Int32 len = r.getLength() + 1; sal_uInt32 needed = nStringOff + len; if( needed > 0xFFFFFF00 ) { bError = true; // out of mem! } else if( needed > nStringSize ) { sal_uInt32 nNewLen = needed + 1024; nNewLen &= 0xFFFFFC00; // trim to 1K border std::unique_ptr p(new sal_Unicode[nNewLen]); memcpy( p.get(), pStrings.get(), nStringSize * sizeof( sal_Unicode ) ); pStrings = std::move(p); nStringSize = sal::static_int_cast< sal_uInt16 >(nNewLen); } if( !bError ) { mvStringOffsets[ nStringIdx++ ] = nStringOff; memcpy( pStrings.get() + nStringOff, r.getStr(), len * sizeof( sal_Unicode ) ); nStringOff = nStringOff + len; // Last String? The update the size of the buffer if( nStringIdx >= mvStringOffsets.size() ) { nStringSize = nStringOff; } } } // Add code block // The block was fetched by the compiler from class SbBuffer and // is already created with new. Additionally it contains all Integers // in Big Endian format, so can be directly read/written. void SbiImage::AddCode(std::vector&& v) { aCode = std::move(v); } // Add user type void SbiImage::AddType(SbxObject const * pObject) { if( !rTypes.is() ) { rTypes = new SbxArray; } SbxObject *pCopyObject = new SbxObject(*pObject); rTypes->Insert(pCopyObject, rTypes->Count()); } void SbiImage::AddEnum(SbxObject* pObject) // Register enum type { if( !rEnums.is() ) { rEnums = new SbxArray; } rEnums->Insert(pObject, rEnums->Count()); } // Note: IDs start with 1 OUString SbiImage::GetString( sal_uInt32 nId, SbxDataType *eType ) const { if( nId && nId <= mvStringOffsets.size() ) { sal_uInt32 nOff = mvStringOffsets[ nId - 1 ]; sal_Unicode* pStr = pStrings.get() + nOff; sal_uInt32 nNextOff = (nId < mvStringOffsets.size()) ? mvStringOffsets[ nId ] : nStringSize; sal_uInt32 nLen = nNextOff - nOff - 1; // #i42467: Special treatment for vbNullChar if (*pStr == 0) { if( nLen == 1 ) { return OUString( u'\0'); } } else { // tdf#143707 - check if the data type character was added after the string termination // symbol. It was added in basic/source/comp/symtbl.cxx. OUString aOUStr(pStr); if (eType != nullptr) { *eType = SbxSTRING; if (o3tl::make_unsigned(aOUStr.getLength()) < nLen) { const sal_Unicode pTypeChar = *(pStrings.get() + nOff + aOUStr.getLength() + 1); switch (pTypeChar) { // See GetSuffixType in basic/source/comp/scanner.cxx for type characters case '%': *eType = SbxINTEGER; break; case '&': *eType = SbxLONG; break; case '!': *eType = SbxSINGLE; break; case '#': *eType = SbxDOUBLE; break; case '@': *eType = SbxCURRENCY; break; // tdf#142460 - properly handle boolean values in string pool case 'b': *eType = SbxBOOL; break; } } } return aOUStr; } } return OUString(); } const SbxObject* SbiImage::FindType (const OUString& aTypeName) const { return rTypes.is() ? static_cast(rTypes->Find(aTypeName,SbxClassType::Object)) : nullptr; } sal_uInt16 SbiImage::CalcLegacyOffset( sal_Int32 nOffset ) { return SbiCodeGen::calcLegacyOffSet(aCode.data(), nOffset); } sal_uInt32 SbiImage::CalcNewOffset( sal_Int16 nOffset ) { return SbiCodeGen::calcNewOffSet(aLegacyPCode.data(), nOffset); } void SbiImage::ReleaseLegacyBuffer() { aLegacyPCode.clear(); } bool SbiImage::ExceedsLegacyLimits() { return (nStringSize > 0xFF00) || (CalcLegacyOffset(aCode.size()) > 0xFF00); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */