/* -*- 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 "propread.hxx"
#include <rtl/tencinfo.h>
#include <rtl/textenc.h>
#include <sal/log.hxx>
#include <o3tl/sorted_vector.hxx>
#include <osl/diagnose.h>
#include <memory>

PropEntry::PropEntry( sal_uInt32 nId, const sal_uInt8* pBuf, sal_uInt32 nBufSize ) :
    mnId        ( nId ),
    mnSize      ( nBufSize ),
    mpBuf       ( new sal_uInt8[ nBufSize ] )
{
    memcpy( mpBuf.get(), pBuf, nBufSize );
};

PropEntry::PropEntry( const PropEntry& rProp ) :
    mnId        ( rProp.mnId ),
    mnSize      ( rProp.mnSize ),
    mpBuf       ( new sal_uInt8[ mnSize ] )
{
    memcpy( mpBuf.get(), rProp.mpBuf.get(), mnSize );
};

PropEntry& PropEntry::operator=(const PropEntry& rPropEntry)
{
    if ( this != &rPropEntry )
    {
        mnId = rPropEntry.mnId;
        mnSize = rPropEntry.mnSize;
        mpBuf.reset( new sal_uInt8[ mnSize ] );
        memcpy( mpBuf.get(), rPropEntry.mpBuf.get(), mnSize );
    }
    return *this;
}

void PropItem::Clear()
{
    Seek( STREAM_SEEK_TO_BEGIN );
    delete[] static_cast<sal_uInt8*>(SwitchBuffer());
}

static sal_Int32 lcl_getMaxSafeStrLen(sal_uInt32 nSize)
{
    nSize -= 1; //Drop NULL terminator

    //If it won't fit in a string, clip it to the max size that does
    if (nSize > SAL_MAX_INT32)
        nSize = SAL_MAX_INT32;

    return static_cast< sal_Int32 >( nSize );
}

bool PropItem::Read( OUString& rString, sal_uInt32 nStringType, bool bAlign )
{
    sal_uInt32 nType, nItemPos;
    bool    bRetValue = false;

    nItemPos = Tell();

    if ( nStringType == VT_EMPTY )
    {
        nType = VT_NULL; // Initialize in case stream fails.
        ReadUInt32( nType );
    }
    else
        nType = nStringType & VT_TYPEMASK;

    sal_uInt32 nItemSize(0); // Initialize in case stream fails.
    ReadUInt32(nItemSize);

    switch( nType )
    {
        case VT_LPSTR :
        {
            if (nItemSize > 0)
            {
                auto nMaxSizePossible = remainingSize();
                if (nItemSize > nMaxSizePossible)
                {
                    SAL_WARN("sd.filter", "String of Len " << nItemSize << " claimed, only " << nMaxSizePossible << " possible");
                    nItemSize = nMaxSizePossible;
                }
            }

            if (nItemSize > 0)
            {
                try
                {
                    std::unique_ptr<char[]> pString( new char[ nItemSize ] );
                    if ( mnTextEnc == RTL_TEXTENCODING_UCS2 )
                    {
                        nItemSize >>= 1;
                        if ( nItemSize > 1 )
                        {
                            sal_Unicode* pWString = reinterpret_cast<sal_Unicode*>(pString.get());
                            for (sal_uInt32 i = 0; i < nItemSize; ++i)
                                ReadUtf16( pWString[ i ] );
                            rString = OUString(pWString, lcl_getMaxSafeStrLen(nItemSize));
                        }
                        else
                            rString.clear();
                        bRetValue = true;
                    }
                    else
                    {
                        SvMemoryStream::ReadBytes(pString.get(), nItemSize);
                        if ( pString[ nItemSize - 1 ] == 0 )
                        {
                            if ( nItemSize > 1 )
                                rString = OUString(pString.get(), rtl_str_getLength(pString.get()), mnTextEnc);
                            else
                                rString.clear();
                            bRetValue = true;
                        }
                    }
                }
                catch( const std::bad_alloc& )
                {
                    OSL_FAIL( "sd PropItem::Read bad alloc" );
                }
            }
            if ( bAlign )
                SeekRel( ( 4 - ( nItemSize & 3 ) ) & 3 );       // dword align
        }
        break;

        case VT_LPWSTR :
        {
            if (nItemSize)
            {
                auto nMaxSizePossible = remainingSize() / sizeof(sal_Unicode);
                if (nItemSize > nMaxSizePossible)
                {
                    SAL_WARN("sd.filter", "String of Len " << nItemSize << " claimed, only " << nMaxSizePossible << " possible");
                    nItemSize = nMaxSizePossible;
                }
            }

            if (nItemSize)
            {
                try
                {
                    std::unique_ptr<sal_Unicode[]> pString( new sal_Unicode[ nItemSize ] );
                    for (sal_uInt32 i = 0; i < nItemSize; ++i)
                        ReadUtf16( pString[ i ] );
                    if ( pString[ nItemSize - 1 ] == 0 )
                    {
                        if ( static_cast<sal_uInt16>(nItemSize) > 1 )
                            rString = OUString(pString.get(), lcl_getMaxSafeStrLen(nItemSize));
                        else
                            rString.clear();
                        bRetValue = true;
                    }
                }
                catch( const std::bad_alloc& )
                {
                    OSL_FAIL( "sd PropItem::Read bad alloc" );
                }
            }
            if ( bAlign && ( nItemSize & 1 ) )
                SeekRel( 2 );                           // dword align
        }
        break;
    }
    if ( !bRetValue )
        Seek( nItemPos );
    return bRetValue;
}

PropItem& PropItem::operator=( PropItem& rPropItem )
{
    if ( this != &rPropItem )
    {
        Seek( STREAM_SEEK_TO_BEGIN );
        delete[] static_cast<sal_uInt8*>(SwitchBuffer());

        mnTextEnc = rPropItem.mnTextEnc;
        SvMemoryStream::WriteBytes(rPropItem.GetData(), rPropItem.TellEnd());
    }
    return *this;
}

Section::Section( const Section& rSection )
    : mnTextEnc(rSection.mnTextEnc)
{
    for ( int i = 0; i < 16; i++ )
        aFMTID[ i ] = rSection.aFMTID[ i ];
    for(const std::unique_ptr<PropEntry>& rEntry : rSection.maEntries)
        maEntries.push_back(std::make_unique<PropEntry>(*rEntry));
}

Section::Section( const sal_uInt8* pFMTID ) : mnTextEnc(RTL_TEXTENCODING_MS_1252)
{
    for ( int i = 0; i < 16; i++ )
        aFMTID[ i ] = pFMTID[ i ];
}

bool Section::GetProperty( sal_uInt32 nId, PropItem& rPropItem )
{
    if ( nId )
    {
        auto iter = std::find_if(maEntries.begin(), maEntries.end(),
            [nId](const std::unique_ptr<PropEntry>& rxEntry) { return rxEntry->mnId == nId; });

        if (iter != maEntries.end())
        {
            rPropItem.Clear();
            rPropItem.SetTextEncoding( mnTextEnc );
            rPropItem.WriteBytes( (*iter)->mpBuf.get(), (*iter)->mnSize );
            rPropItem.Seek( STREAM_SEEK_TO_BEGIN );
            return true;
        }
    }
    return false;
}

void Section::AddProperty( sal_uInt32 nId, const sal_uInt8* pBuf, sal_uInt32 nBufSize )
{
    // just a simple id check

    if ( !nId )
        return;
    if ( nId == 0xffffffff )
        nId = 0;

    // do not allow same PropId's, sort
    auto iter = std::find_if(maEntries.begin(), maEntries.end(),
        [nId](const std::unique_ptr<PropEntry>& rxEntry) { return rxEntry->mnId >= nId; });
    if (iter != maEntries.end())
    {
        if ( (*iter)->mnId == nId )
            (*iter).reset(new PropEntry( nId, pBuf, nBufSize ));
        else
            maEntries.insert( iter, std::make_unique<PropEntry>( nId, pBuf, nBufSize ));
    }
    else
    {
        maEntries.push_back( std::make_unique<PropEntry>( nId, pBuf, nBufSize ) );
    }
}

void Section::GetDictionary(PropDictionary& rDict)
{
    auto iter = std::find_if(maEntries.begin(), maEntries.end(),
        [](const std::unique_ptr<PropEntry>& rxEntry) { return rxEntry->mnId == 0; });

    if (iter == maEntries.end())
        return;

    SvMemoryStream aStream( (*iter)->mpBuf.get(), (*iter)->mnSize, StreamMode::READ );
    aStream.Seek( STREAM_SEEK_TO_BEGIN );
    sal_uInt32 nDictCount(0);
    aStream.ReadUInt32( nDictCount );
    for (sal_uInt32 i = 0; i < nDictCount; ++i)
    {
        sal_uInt32 nId(0), nSize(0);
        aStream.ReadUInt32(nId).ReadUInt32(nSize);
        if (!aStream.good() || nSize > aStream.remainingSize())
            break;
        if (mnTextEnc == RTL_TEXTENCODING_UCS2)
            nSize >>= 1;
        if (!nSize)
            continue;
        OUString aString;
        try
        {
            if ( mnTextEnc == RTL_TEXTENCODING_UCS2 )
            {
                std::unique_ptr<sal_Unicode[]> pWString( new sal_Unicode[nSize] );
                for (sal_uInt32 j = 0; j < nSize; ++j)
                    aStream.ReadUtf16(pWString[j]);
                aString = OUString(pWString.get(), lcl_getMaxSafeStrLen(nSize));
            }
            else
            {
                std::unique_ptr<char[]> pString( new char[nSize] );
                aStream.ReadBytes(pString.get(), nSize);
                aString = OUString(pString.get(), lcl_getMaxSafeStrLen(nSize), mnTextEnc);
            }
        }
        catch( const std::bad_alloc& )
        {
            OSL_FAIL( "sd Section::GetDictionary bad alloc" );
        }
        if (aString.isEmpty())
            break;
        rDict.insert( std::make_pair(aString,nId) );
    }
}

void Section::Read( SotStorageStream *pStrm )
{
    sal_uInt64 nSecOfs = pStrm->Tell();
    sal_uInt64 nStrmSize = pStrm->remainingSize();

    mnTextEnc = RTL_TEXTENCODING_MS_1252;
    sal_uInt32 nSecSize(0), nPropCount(0);
    pStrm->ReadUInt32(nSecSize).ReadUInt32(nPropCount);
    if (nSecSize > nStrmSize)
    {
        SAL_WARN("sd.filter", "Section Len " << nSecSize << " claimed, only " << nStrmSize << " possible");
        nSecSize = nStrmSize;
    }

    while (nPropCount > 0)
    {
        --nPropCount;
        sal_uInt32 nPropId(0), nPropOfs(0);
        pStrm->ReadUInt32(nPropId).ReadUInt32(nPropOfs);
        if (!pStrm->good())
            break;
        auto nCurrent = pStrm->Tell();
        sal_uInt64 nOffset = nPropOfs + nSecOfs;
        if (!checkSeek(*pStrm, nOffset))
            break;
        if ( nPropId )                  // do not read dictionary
        {
            sal_uInt32 nPropType(0), nVectorCount(0);
            pStrm->ReadUInt32(nPropType);

            sal_uInt32 nPropSize = 4;
            if ( nPropType & VT_VECTOR )
            {
                pStrm->ReadUInt32( nVectorCount );
                nPropType &=~VT_VECTOR;
                nPropSize += 4;
            }
            else
                nVectorCount = 1;

            bool bVariant = ( nPropType == VT_VARIANT );

            o3tl::sorted_vector<sal_uInt64> aVisitedOffsets;

            for (sal_uInt32 i = 0; nPropSize && i < nVectorCount && pStrm->good(); ++i)
            {
                if ( bVariant )
                {
                    pStrm->ReadUInt32( nPropType );
                    nPropSize += 4;
                }
                sal_uInt32 nTemp(0);
                switch( nPropType )
                {
                    case VT_UI1 :
                        nPropSize++;
                    break;

                    case VT_I2 :
                    case VT_UI2 :
                    case VT_BOOL :
                        nPropSize += 2;
                    break;

                    case VT_I4 :
                    case VT_R4 :
                    case VT_UI4 :
                    case VT_ERROR :
                        nPropSize += 4;
                    break;

                    case VT_I8 :
                    case VT_R8 :
                    case VT_CY :
                    case VT_UI8 :
                    case VT_DATE :
                    case VT_FILETIME :
                        nPropSize += 8;
                    break;

                    case VT_BSTR :
                        pStrm->ReadUInt32( nTemp );
                        nPropSize += ( nTemp + 4 );
                    break;

                    case VT_LPSTR :
                        pStrm->ReadUInt32( nTemp );
                        nPropSize += ( nTemp + 4 );
                    break;

                    case VT_LPWSTR :
                        {
                        pStrm->ReadUInt32( nTemp );
                        // looks like these are aligned to 4 bytes
                        sal_uInt64 nLength = nPropOfs + nSecOfs + nPropSize + ( nTemp << 1 ) + 4;
                        nPropSize += ( nTemp << 1 ) + 4 + (nLength % 4);
                        }
                    break;

                    case VT_BLOB_OBJECT :
                    case VT_BLOB :
                    case VT_CF :
                        pStrm->ReadUInt32( nTemp );
                        nPropSize += ( nTemp + 4 );
                    break;

                    case VT_CLSID :
                    case VT_STREAM :
                    case VT_STORAGE :
                    case VT_STREAMED_OBJECT :
                    case VT_STORED_OBJECT :
                    case VT_VARIANT :
                    case VT_VECTOR :
                    default :
                        nPropSize = 0;
                }
                if ( nPropSize )
                {
                    if ( ( nVectorCount - i ) > 1 )
                    {
                        nOffset = nPropOfs + nSecOfs + nPropSize;
                        if (!checkSeek(*pStrm, nOffset))
                            break;
                        // inserts returns false if an equivalent element already existed
                        if (!aVisitedOffsets.insert(nOffset).second)
                        {
                            SAL_WARN("sd.filter", "loop in Section::Read property list");
                            break;
                        }
                    }
                }
                else
                    break;
            }
            if ( nPropSize )
            {
                if ( nPropSize > nStrmSize )
                {
                    break;
                }
                pStrm->Seek( nPropOfs + nSecOfs );
                // make sure we don't overflow the section size
                if( nPropSize > nSecSize - nSecOfs )
                    nPropSize = nSecSize - nSecOfs;
                std::unique_ptr<sal_uInt8[]> pBuf( new sal_uInt8[ nPropSize ] );
                nPropSize = pStrm->ReadBytes(pBuf.get(), nPropSize);
                AddProperty( nPropId, pBuf.get(), nPropSize );
            }
            if ( nPropId == 1 )
            {
                PropItem aPropItem;
                if ( GetProperty( 1, aPropItem ) )
                {
                    aPropItem.ReadUInt32( nPropType );
                    if ( nPropType == VT_I2 )
                    {
                        sal_uInt16 nCodePage(0);
                        aPropItem.ReadUInt16(nCodePage);

                        if ( nCodePage == 1200 )
                        {
                            mnTextEnc = RTL_TEXTENCODING_UCS2;
                        }
                        else
                        {
                            mnTextEnc = rtl_getTextEncodingFromWindowsCodePage( nCodePage );
                            if ( mnTextEnc == RTL_TEXTENCODING_DONTKNOW )
                                mnTextEnc = RTL_TEXTENCODING_MS_1252;
                        }
                    }
                    else
                    {
                        mnTextEnc = RTL_TEXTENCODING_MS_1252;
                    }
                }
            }
        }
        else
        {
            sal_uInt32 nDictCount(0);
            pStrm->ReadUInt32(nDictCount);
            auto nMaxRecordsPossible = pStrm->remainingSize() / (sizeof(sal_uInt32)*2);
            if (nDictCount > nMaxRecordsPossible)
            {
                SAL_WARN("sd.filter", "Dictionary count of " << nDictCount << " claimed, only " << nMaxRecordsPossible << " possible");
                nDictCount = nMaxRecordsPossible;
            }
            for (sal_uInt32 i = 0; i < nDictCount; ++i)
            {
                sal_uInt32 nSize(0);
                pStrm->ReadUInt32( nSize ).ReadUInt32( nSize );
                if (!pStrm->good())
                    break;
                sal_uInt64 nPos = pStrm->Tell() + nSize;
                if (!checkSeek(*pStrm, nPos))
                    break;
            }
            sal_uInt64 nSize = pStrm->Tell();
            pStrm->Seek( nPropOfs + nSecOfs );
            nSize -= pStrm->Tell();
            if ( nSize > nStrmSize )
            {
                break;
            }
            std::unique_ptr<sal_uInt8[]> pBuf( new sal_uInt8[ nSize ] );
            nSize = pStrm->ReadBytes(pBuf.get(), nSize);
            AddProperty( 0xffffffff, pBuf.get(), nSize );
        }
        pStrm->Seek(nCurrent);
    }
    pStrm->Seek(nSecOfs + nSecSize);
}

Section& Section::operator=( const Section& rSection )
{
    if ( this != &rSection )
    {
        memcpy( static_cast<void*>(aFMTID), static_cast<void const *>(rSection.aFMTID), 16 );

        for(const std::unique_ptr<PropEntry>& rEntry : rSection.maEntries)
            maEntries.push_back(std::make_unique<PropEntry>(*rEntry));
    }
    return *this;
}

PropRead::PropRead( SotStorage& rStorage, const OUString& rName ) :
        mbStatus            ( false ),
        mnByteOrder         ( 0xfffe )
{
    if ( rStorage.IsStream( rName ) )
    {
        mpSvStream = rStorage.OpenSotStream( rName, StreamMode::STD_READ );
        if ( mpSvStream.is() )
        {
            mpSvStream->SetEndian( SvStreamEndian::LITTLE );
            memset( mApplicationCLSID, 0, 16 );
            mbStatus = true;
        }
    }
}

const Section* PropRead::GetSection( const sal_uInt8* pFMTID )
{
    auto it = std::find_if(maSections.begin(), maSections.end(),
        [&pFMTID](const std::unique_ptr<Section>& rxSection) { return memcmp( rxSection->GetFMTID(), pFMTID, 16 ) == 0; });
    if (it != maSections.end())
        return it->get();
    return nullptr;
}

void PropRead::Read()
{
    maSections.clear();

    if ( !mbStatus )
        return;

    sal_uInt16              mnVersionLo;
    sal_uInt16              mnVersionHi;
    sal_uInt16              mnFormat;
    mpSvStream->ReadUInt16( mnByteOrder ).ReadUInt16( mnFormat ).ReadUInt16( mnVersionLo ).ReadUInt16( mnVersionHi );
    if ( mnByteOrder != 0xfffe )
        return;

    std::vector<sal_uInt8> aSectCLSID(16);
    mpSvStream->ReadBytes(mApplicationCLSID, 16);
    sal_uInt32 nSections(0);
    mpSvStream->ReadUInt32(nSections);
    if ( nSections > 2 )                // sj: PowerPoint documents are containing max 2 sections
    {
        mbStatus = false;
    }
    else
        for ( sal_uInt32 i = 0; i < nSections; i++ )
        {
            mpSvStream->ReadBytes(aSectCLSID.data(), aSectCLSID.size());
            sal_uInt32 nSectionOfs(0);
            mpSvStream->ReadUInt32( nSectionOfs );
            sal_uInt64 nCurrent = mpSvStream->Tell();
            if (checkSeek(*mpSvStream, nSectionOfs))
            {
                Section aSection(aSectCLSID.data());
                aSection.Read(mpSvStream.get());
                maSections.push_back(std::make_unique<Section>(aSection));
            }
            mpSvStream->Seek( nCurrent );
        }
}

PropRead& PropRead::operator=( const PropRead& rPropRead )
{
    if ( this != &rPropRead )
    {
        mbStatus = rPropRead.mbStatus;
        mpSvStream = rPropRead.mpSvStream;

        mnByteOrder = rPropRead.mnByteOrder;
        memcpy( mApplicationCLSID, rPropRead.mApplicationCLSID, 16 );

        for(const std::unique_ptr<Section>& rSection : rPropRead.maSections)
            maSections.push_back(std::make_unique<Section>(*rSection));
    }
    return *this;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
