/* -*- 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"

PropEntry::PropEntry( sal_uInt32 nId, const sal_uInt8* pBuf, sal_uInt32 nBufSize, sal_uInt16 nTextEnc ) :
    mnId        ( nId ),
    mnSize      ( nBufSize ),
    mnTextEnc   ( nTextEnc ),
    mpBuf       ( new sal_uInt8[ nBufSize ] )
{
    memcpy( (void*)mpBuf, (void*)pBuf, nBufSize );
};

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

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



void PropItem::Clear()
{
    Seek( STREAM_SEEK_TO_BEGIN );
    delete[] (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  i, nItemSize, 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;

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

    switch( nType )
    {
        case VT_LPSTR :
        {
            if ( nItemSize )
            {
                try
                {
                    sal_Char* pString = new sal_Char[ nItemSize ];
                    if ( mnTextEnc == RTL_TEXTENCODING_UCS2 )
                    {
                        nItemSize >>= 1;
                        if ( nItemSize > 1 )
                        {
                            sal_Unicode* pWString = (sal_Unicode*)pString;
                            for ( i = 0; i < nItemSize; i++ )
                                ReadUInt16( pWString[ i ] );
                            rString = OUString(pWString, lcl_getMaxSafeStrLen(nItemSize));
                        }
                        else
                            rString = OUString();
                        bRetValue = true;
                    }
                    else
                    {
                        SvMemoryStream::Read( pString, nItemSize );
                        if ( pString[ nItemSize - 1 ] == 0 )
                        {
                            if ( nItemSize > 1 )
                                rString = OUString(pString, rtl_str_getLength(pString), mnTextEnc);
                            else
                                rString = OUString();
                            bRetValue = true;
                        }
                    }
                    delete[] pString;
                }
                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 )
            {
                try
                {
                    sal_Unicode* pString = new sal_Unicode[ nItemSize ];
                    for ( i = 0; i < nItemSize; i++ )
                        ReadUInt16( pString[ i ] );
                    if ( pString[ i - 1 ] == 0 )
                    {
                        if ( (sal_uInt16)nItemSize > 1 )
                            rString = OUString(pString, lcl_getMaxSafeStrLen(nItemSize));
                        else
                            rString = OUString();
                        bRetValue = true;
                    }
                    delete[] pString;
                }
                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[] (sal_uInt8*)SwitchBuffer();

        mnTextEnc = rPropItem.mnTextEnc;
        sal_uInt32 nItemPos = rPropItem.Tell();
        rPropItem.Seek( STREAM_SEEK_TO_END );
        SvMemoryStream::Write( rPropItem.GetData(), rPropItem.Tell() );
        rPropItem.Seek( nItemPos );
    }
    return *this;
}



Section::Section( const Section& rSection )
    : mnTextEnc(rSection.mnTextEnc),
    maEntries(rSection.maEntries.clone())
{
    for ( int i = 0; i < 16; i++ )
        aFMTID[ i ] = rSection.aFMTID[ i ];
}



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 )
    {
        boost::ptr_vector<PropEntry>::const_iterator iter;
        for (iter = maEntries.begin(); iter != maEntries.end(); ++iter)
        {
            if (iter->mnId == nId)
                break;
        }

        if (iter != maEntries.end())
        {
            rPropItem.Clear();
            rPropItem.SetTextEncoding( mnTextEnc );
            rPropItem.Write( iter->mpBuf,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
    boost::ptr_vector<PropEntry>::iterator iter;
    for ( iter = maEntries.begin(); iter != maEntries.end(); ++iter )
    {
        if ( iter->mnId == nId )
            maEntries.replace( iter, new PropEntry( nId, pBuf, nBufSize, mnTextEnc ));
        else if ( iter->mnId > nId )
            maEntries.insert( iter, new PropEntry( nId, pBuf, nBufSize, mnTextEnc ));
        else
            continue;
        return;
    }

    maEntries.push_back( new PropEntry( nId, pBuf, nBufSize, mnTextEnc ) );
}



bool Section::GetDictionary( Dictionary& rDict )
{
    bool bRetValue = false;

    boost::ptr_vector<PropEntry>::iterator iter;
    for (iter = maEntries.begin(); iter != maEntries.end(); ++iter)
    {
        if ( iter->mnId == 0 )
            break;
    }

    if ( iter != maEntries.end() )
    {
        sal_uInt32 nDictCount, nId, nSize, nPos;
        SvMemoryStream aStream( (sal_Int8*)iter->mpBuf, iter->mnSize, STREAM_READ );
        aStream.Seek( STREAM_SEEK_TO_BEGIN );
        aStream.ReadUInt32( nDictCount );
        for ( sal_uInt32 i = 0; i < nDictCount; i++ )
        {
            aStream.ReadUInt32( nId ).ReadUInt32( nSize );
            if ( nSize )
            {
                OUString aString;
                nPos = aStream.Tell();
                try
                {
                    sal_Char* pString = new sal_Char[ nSize ];
                    aStream.Read( pString, nSize );
                    if ( mnTextEnc == RTL_TEXTENCODING_UCS2 )
                    {
                        nSize >>= 1;
                        aStream.Seek( nPos );
                        sal_Unicode* pWString = (sal_Unicode*)pString;
                        for ( i = 0; i < nSize; i++ )
                            aStream.ReadUInt16( pWString[ i ] );
                        aString = OUString(pWString, lcl_getMaxSafeStrLen(nSize));
                    }
                    else
                        aString = OUString(pString, lcl_getMaxSafeStrLen(nSize), mnTextEnc);
                    delete[] pString;
                }
                catch( const std::bad_alloc& )
                {
                    OSL_FAIL( "sd Section::GetDictionary bad alloc" );
                }
                if ( aString.isEmpty() )
                    break;
                rDict.insert( std::make_pair(aString,nId) );
            }
            bRetValue = true;
        }
    }
    return bRetValue;
}



void Section::Read( SvStorageStream *pStrm )
{
    sal_uInt32 i, nSecOfs, nSecSize, nPropCount, nPropId, nPropOfs, nPropType, nPropSize, nCurrent, nVectorCount, nTemp, nStrmSize;
    nSecOfs = pStrm->Tell();

    pStrm->Seek( STREAM_SEEK_TO_END );
    nStrmSize = pStrm->Tell();
    pStrm->Seek( nSecOfs );

    mnTextEnc = RTL_TEXTENCODING_MS_1252;
    pStrm->ReadUInt32( nSecSize ).ReadUInt32( nPropCount );
    while( nPropCount-- && ( pStrm->GetError() == ERRCODE_NONE ) )
    {
        pStrm->ReadUInt32( nPropId ).ReadUInt32( nPropOfs );
        nCurrent = pStrm->Tell();
        pStrm->Seek( nPropOfs + nSecOfs );
        if ( nPropId )                  // do not read dictionary
        {

            pStrm->ReadUInt32( nPropType );

            nPropSize = 4;

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


            bool bVariant = ( nPropType == VT_VARIANT );

            for ( i = 0; nPropSize && ( i < nVectorCount ); i++ )
            {
                if ( bVariant )
                {
                    pStrm->ReadUInt32( nPropType );
                    nPropSize += 4;
                }
                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_uInt32 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 )
                        pStrm->Seek( nPropOfs + nSecOfs + nPropSize );
                }
                else
                    break;
            }
            if ( nPropSize )
            {
                if ( nPropSize > nStrmSize )
                {
                    nPropCount = 0;
                    break;
                }
                pStrm->Seek( nPropOfs + nSecOfs );
                // make sure we don't overflow the section size
                if( nPropSize > nSecSize - nSecOfs )
                    nPropSize = nSecSize - nSecOfs;
                sal_uInt8* pBuf = new sal_uInt8[ nPropSize ];
                pStrm->Read( pBuf, nPropSize );
                AddProperty( nPropId, pBuf, nPropSize );
                delete[] pBuf;
            }
            if ( nPropId == 1 )
            {
                PropItem aPropItem;
                if ( GetProperty( 1, aPropItem ) )
                {
                    sal_uInt16 nCodePage;
                    aPropItem.ReadUInt32( nPropType );
                    if ( nPropType == VT_I2 )
                    {
                        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, nSize;
            pStrm->ReadUInt32( nDictCount );
            for ( i = 0; i < nDictCount; i++ )
            {
                pStrm->ReadUInt32( nSize ).ReadUInt32( nSize );
                pStrm->SeekRel( nSize );
            }
            nSize = pStrm->Tell();
            pStrm->Seek( nPropOfs + nSecOfs );
            nSize -= pStrm->Tell();
            if ( nSize > nStrmSize )
            {
                nPropCount = 0;
                break;
            }
            sal_uInt8* pBuf = new sal_uInt8[ nSize ];
            pStrm->Read( pBuf, nSize );
            AddProperty( 0xffffffff, pBuf, nSize );
            delete[] pBuf;
        }
        pStrm->Seek( nCurrent );
    }
    pStrm->Seek( nSecOfs + nSecSize );
}



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

        maEntries = rSection.maEntries.clone();
    }
    return *this;
}



PropRead::PropRead( SvStorage& rStorage, const OUString& rName ) :
        mbStatus            ( false ),
        mnByteOrder         ( 0xfffe ),
        mnFormat            ( 0 ),
        mnVersionLo         ( 4 ),
        mnVersionHi         ( 2 )
{
    if ( rStorage.IsStream( rName ) )
    {
        mpSvStream = rStorage.OpenSotStream( rName, STREAM_STD_READ );
        if ( mpSvStream )
        {
            mpSvStream->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN );
            memset( mApplicationCLSID, 0, 16 );
            mbStatus = true;
        }
    }
}



void PropRead::AddSection( Section& rSection )
{
    maSections.push_back( new Section( rSection ) );
}



const Section* PropRead::GetSection( const sal_uInt8* pFMTID )
{
    boost::ptr_vector<Section>::iterator it;
    for ( it = maSections.begin(); it != maSections.end(); ++it)
    {
        if ( memcmp( it->GetFMTID(), pFMTID, 16 ) == 0 )
            return &(*it);
    }
    return NULL;
}



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

    if ( mbStatus )
    {
        sal_uInt32  nSections;
        sal_uInt32  nSectionOfs;
        sal_uInt32  nCurrent;
        mpSvStream->ReadUInt16( mnByteOrder ).ReadUInt16( mnFormat ).ReadUInt16( mnVersionLo ).ReadUInt16( mnVersionHi );
        if ( mnByteOrder == 0xfffe )
        {
            sal_uInt8*  pSectCLSID = new sal_uInt8[ 16 ];
            mpSvStream->Read( mApplicationCLSID, 16 );
            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->Read( pSectCLSID, 16 );
                mpSvStream->ReadUInt32( nSectionOfs );
                nCurrent = mpSvStream->Tell();
                mpSvStream->Seek( nSectionOfs );
                Section aSection( pSectCLSID );
                aSection.Read( mpSvStream );
                AddSection( aSection );
                mpSvStream->Seek( nCurrent );
            }
            delete[] pSectCLSID;
        }
    }
}



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

        mnByteOrder = rPropRead.mnByteOrder;
        mnFormat = rPropRead.mnFormat;
        mnVersionLo = rPropRead.mnVersionLo;
        mnVersionHi = rPropRead.mnVersionHi;
        memcpy( mApplicationCLSID, rPropRead.mApplicationCLSID, 16 );

        maSections = rPropRead.maSections.clone();
    }
    return *this;
}

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