/* -*- 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/.
 */
 
#include <sal/config.h>
 
#include <cassert>
#include <cmath>
#include <random>
 
#include <oox/ole/vbaexport.hxx>
 
#include <tools/stream.hxx>
 
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/script/XLibraryContainer.hpp>
#include <com/sun/star/script/vba/XVBAModuleInfo.hpp>
#include <com/sun/star/script/vba/XVBACompatibility.hpp>
#include <com/sun/star/frame/XModel.hpp>
 
#include <ooo/vba/excel/XWorkbook.hpp>
 
#include <oox/helper/binaryoutputstream.hxx>
#include <oox/helper/propertyset.hxx>
#include <oox/token/properties.hxx>
 
#include <sot/storage.hxx>
 
#include <comphelper/xmltools.hxx>
 
#define USE_UTF8_CODEPAGE 0
#if USE_UTF8_CODEPAGE
#define CODEPAGE_MS 65001
#define CODEPAGE RTL_TEXTENCODING_UTF8
#else
#define CODEPAGE_MS 1252
#define CODEPAGE RTL_TEXTENCODING_MS_1252
#endif
 
#define VBA_EXPORT_DEBUG 0
#define VBA_USE_ORIGINAL_WM_STREAM 0
#define VBA_USE_ORIGINAL_DIR_STREAM 0
#define VBA_USE_ORIGINAL_PROJECT_STREAM 0
#define VBA_USE_ORIGINAL_VBA_PROJECT 0
 
/* Enable to see VBA Encryption work. For now the input data and length values
 * for encryption correspond to the case when the VBA macro is not protected.
 */
#define VBA_ENCRYPTION 1
 
namespace {
 
void exportString(SvStream& rStrm, const OUString& rString)
{
    OString aStringCorrectCodepage = OUStringToOString(rString, CODEPAGE);
    rStrm.WriteOString(aStringCorrectCodepage);
}
 
void exportUTF16String(SvStream& rStrm, const OUString& rString)
{
    sal_Int32 n = rString.getLength();
    const sal_Unicode* pString = rString.getStr();
    for (sal_Int32 i = 0; i < n; ++i)
    {
        sal_Unicode character = pString[i];
        rStrm.WriteUnicode(character);
    }
}
 
bool isWorkbook(const css::uno::Reference<css::uno::XInterface>& xInterface)
{
    css::uno::Reference<ooo::vba::excel::XWorkbook> xWorkbook(xInterface, css::uno::UNO_QUERY);
    return xWorkbook.is();
}
 
OUString createHexStringFromDigit(sal_uInt8 nDigit)
{
    OUString aString = OUString::number( nDigit, 16 );
    if(aString.getLength() == 1)
        aString = OUString::number(0) + aString;
    return aString.toAsciiUpperCase();
}
 
}
 
VBACompressionChunk::VBACompressionChunk(SvStream& rCompressedStream, const sal_uInt8* pData, std::size_t nChunkSize)
    : mrCompressedStream(rCompressedStream)
    , mpUncompressedData(pData)
    , mpCompressedChunkStream(nullptr)
    , mnChunkSize(nChunkSize)
    , mnCompressedCurrent(0)
    , mnCompressedEnd(0)
    , mnDecompressedCurrent(0)
    , mnDecompressedEnd(0)
{
}
 
void setUInt16(sal_uInt8* pBuffer, size_t nPos, sal_uInt16 nVal)
{
    pBuffer[nPos] = nVal & 0xFF;
    pBuffer[nPos+1] = (nVal & 0xFF00) >> 8;
}
 
sal_uInt16 VBACompressionChunk::handleHeader(bool bCompressed)
{
    // handle header bytes
    size_t nSize = mnCompressedCurrent;
    sal_uInt16 nHeader = 0;
    PackCompressedChunkSize(nSize, nHeader);
    PackCompressedChunkFlag(bCompressed, nHeader);
    PackCompressedChunkSignature(nHeader);
 
    return nHeader;
}
 
// section 2.4.1.3.7
void VBACompressionChunk::write()
{
 
    mnDecompressedCurrent = 0;
    mnCompressedCurrent = 2;
    mnCompressedEnd = 4098;
    mnDecompressedEnd = std::min<sal_uInt64>(4096, mnChunkSize);
 
    // if that stream becomes larger than 4096 bytes then
    // we use the uncompressed stream
    sal_uInt8 pCompressedChunkStream[4098];
    mpCompressedChunkStream = pCompressedChunkStream;
 
    while (mnDecompressedCurrent < mnDecompressedEnd
            && mnCompressedCurrent < mnCompressedEnd)
    {
        // compress token sequence
        compressTokenSequence();
    }
 
    if (mnDecompressedCurrent < mnDecompressedEnd)
    {
        sal_uInt64 nChunkStart = mrCompressedStream.Tell();
        mrCompressedStream.WriteUInt16(0);
        writeRawChunk();
        mrCompressedStream.Seek(nChunkStart);
        sal_uInt16 nHeader = handleHeader(false);
        mrCompressedStream.WriteUInt16(nHeader);
    }
    else
    {
        sal_uInt16 nHeader = handleHeader(true);
        setUInt16(pCompressedChunkStream, 0, nHeader);
        // copy the compressed stream to our output stream
        mrCompressedStream.WriteBytes(pCompressedChunkStream, mnCompressedCurrent);
    }
}
 
// section 2.4.1.3.13
void VBACompressionChunk::PackCompressedChunkSize(size_t nSize, sal_uInt16& rHeader)
{
    sal_uInt16 nTemp1 = rHeader & 0xF000;
    sal_uInt16 nTemp2 = nSize - 3;
    rHeader = nTemp1 | nTemp2;
}
 
// section 2.4.1.3.16
void VBACompressionChunk::PackCompressedChunkFlag(bool bCompressed, sal_uInt16& rHeader)
{
    sal_uInt16 nTemp1 = rHeader & 0x7FFF;
    sal_uInt16 nTemp2 = static_cast<sal_uInt16>(bCompressed) << 15;
    rHeader = nTemp1 | nTemp2;
}
 
// section 2.4.1.3.14
void VBACompressionChunk::PackCompressedChunkSignature(sal_uInt16& rHeader)
{
    sal_Int32 nTemp = rHeader & 0x8FFFF;
    rHeader = nTemp | 0x3000;
}
 
// section 2.4.1.3.8
void VBACompressionChunk::compressTokenSequence()
{
    sal_uInt64 nFlagByteIndex = mnCompressedCurrent;
    sal_uInt8 nFlagByte = 0;
    ++mnCompressedCurrent;
    for (size_t index = 0; index <= 7; ++index)
    {
        if (mnDecompressedCurrent < mnDecompressedEnd
                && mnCompressedCurrent < mnCompressedEnd)
        {
            compressToken(index, nFlagByte);
        }
    }
    mpCompressedChunkStream[nFlagByteIndex] = nFlagByte;
}
 
// section 2.4.1.3.9
void VBACompressionChunk::compressToken(size_t index, sal_uInt8& nFlagByte)
{
    size_t nLength = 0;
    size_t nOffset = 0;
    match(nLength, nOffset);
    if (nOffset != 0)
    {
        if (mnCompressedCurrent + 1 < mnCompressedEnd)
        {
            sal_uInt16 nToken = CopyToken(nLength, nOffset);
            setUInt16(mpCompressedChunkStream, mnCompressedCurrent, nToken);
            SetFlagBit(index, true, nFlagByte);
            mnCompressedCurrent += 2;
            mnDecompressedCurrent += nLength;
        }
        else
        {
            mnCompressedCurrent = mnCompressedEnd;
        }
    }
    else
    {
        if (mnCompressedCurrent + 1 < mnCompressedEnd)
        {
            mpCompressedChunkStream[mnCompressedCurrent] = mpUncompressedData[mnDecompressedCurrent];
            ++mnCompressedCurrent;
            ++mnDecompressedCurrent;
        }
        else
        {
            mnCompressedCurrent = mnCompressedEnd;
        }
    }
}
 
// section 2.4.1.3.18
void VBACompressionChunk::SetFlagBit(size_t index, bool bVal, sal_uInt8& rFlag)
{
    size_t nTemp1 = static_cast<int>(bVal) << index;
    sal_uInt8 nTemp2 = rFlag & (~nTemp1);
    rFlag = nTemp2 | nTemp1;
}
 
// section 2.4.1.3.19.3
sal_uInt16 VBACompressionChunk::CopyToken(size_t nLength, size_t nOffset)
{
    sal_uInt16 nLengthMask = 0;
    sal_uInt16 nOffsetMask = 0;
    sal_uInt16 nBitCount = 0;
    sal_uInt16 nMaxLength;
    CopyTokenHelp(nLengthMask, nOffsetMask, nBitCount, nMaxLength);
    sal_uInt16 nTemp1 = nOffset -1;
    sal_uInt16 nTemp2 = 16 - nBitCount;
    sal_uInt16 nTemp3 = nLength - 3;
    sal_uInt16 nToken = (nTemp1 << nTemp2) | nTemp3;
    return nToken;
}
 
// section 2.4.1.3.19.4
void VBACompressionChunk::match(size_t& rLength, size_t& rOffset)
{
    size_t nBestLen = 0;
    sal_Int32 nCandidate = mnDecompressedCurrent - 1;
    sal_Int32 nBestCandidate = nCandidate;
    while (nCandidate >= 0)
    {
        sal_Int32 nC = nCandidate;
        sal_Int32 nD = mnDecompressedCurrent;
        size_t nLen = 0;
        while (nD < static_cast<sal_Int32>(mnChunkSize) // TODO: check if this needs to be including a minus -1
                && mpUncompressedData[nC] == mpUncompressedData[nD])
        {
            ++nLen;
            ++nC;
            ++nD;
        }
        if (nLen > nBestLen)
        {
            nBestLen = nLen;
            nBestCandidate = nCandidate;
        }
        --nCandidate;
    }
 
    if (nBestLen >= 3)
    {
        sal_uInt16 nMaximumLength = 0;
        sal_uInt16 nLengthMask, nOffsetMask, nBitCount;
        CopyTokenHelp(nLengthMask, nOffsetMask, nBitCount, nMaximumLength);
        rLength = std::min<sal_uInt16>(nMaximumLength, nBestLen);
        rOffset = mnDecompressedCurrent - nBestCandidate;
    }
    else
    {
        rLength = 0;
        rOffset = 0;
    }
}
 
// section 2.4.1.3.19.1
void VBACompressionChunk::CopyTokenHelp(sal_uInt16& rLengthMask, sal_uInt16& rOffsetMask,
        sal_uInt16& rBitCount, sal_uInt16& rMaximumLength)
{
    sal_uInt16 nDifference = mnDecompressedCurrent;
    assert(nDifference <= 4096);
    assert(nDifference >= 1);
    if (nDifference >= 2049)
        rBitCount = 12;
    else if (nDifference >= 1025)
        rBitCount = 11;
    else if (nDifference >= 513)
        rBitCount = 10;
    else if (nDifference >= 257)
        rBitCount = 9;
    else if (nDifference >= 129)
        rBitCount = 8;
    else if (nDifference >= 65)
        rBitCount = 7;
    else if (nDifference >= 33)
        rBitCount = 6;
    else if (nDifference >= 17)
        rBitCount = 5;
    else
        rBitCount = 4;
    rLengthMask = 0xffff >> rBitCount;
    rOffsetMask = ~rLengthMask;
    rMaximumLength = rLengthMask + 3;
}
 
// section 2.4.1.3.10
void VBACompressionChunk::writeRawChunk()
{
    // we need to use up to 4096 bytes of the original stream
    // and fill the rest with padding
    mrCompressedStream.WriteBytes(mpUncompressedData, mnChunkSize);
    std::size_t nPadding = 4096 - mnChunkSize;
    for (size_t i = 0; i < nPadding; ++i)
    {
        mrCompressedStream.WriteUInt8(0);
    }
}
 
VBACompression::VBACompression(SvStream& rCompressedStream,
        SvMemoryStream& rUncompressedStream):
    mrCompressedStream(rCompressedStream),
    mrUncompressedStream(rUncompressedStream)
{
}
 
// section 2.4.1.3.6
void VBACompression::write()
{
    // section 2.4.1.1.1
    mrCompressedStream.WriteUInt8(0x01); // signature byte of a compressed container
    bool bStreamNotEnded = true;
    const sal_uInt8* pData = static_cast<const sal_uInt8*>(mrUncompressedStream.GetData());
    std::size_t nSize = mrUncompressedStream.GetEndOfData();
    std::size_t nRemainingSize = nSize;
    while(bStreamNotEnded)
    {
        std::size_t nChunkSize = std::min<size_t>(nRemainingSize, 4096);
        VBACompressionChunk aChunk(mrCompressedStream, &pData[nSize - nRemainingSize], nChunkSize);
        aChunk.write();
 
        // update the uncompressed chunk start marker
        nRemainingSize -= nChunkSize;
        bStreamNotEnded = nRemainingSize != 0;
    }
}
 
// section 2.4.3
#if VBA_ENCRYPTION
 
VBAEncryption::VBAEncryption(const sal_uInt8* pData, const sal_uInt16 length, SvStream& rEncryptedData, sal_uInt8 nProjKey)
    :mpData(pData)
    ,mnLength(length)
    ,mrEncryptedData(rEncryptedData)
    ,mnUnencryptedByte1(0)
    ,mnEncryptedByte1(0)
    ,mnEncryptedByte2(0)
    ,mnProjKey(nProjKey)
    ,mnIgnoredLength(0)
    ,mnSeed(0x00)
    ,mnVersionEnc(0)
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 255);
    mnSeed = dis(gen);
}
 
void VBAEncryption::writeSeed()
{
    exportString(mrEncryptedData, createHexStringFromDigit(mnSeed));
}
 
void VBAEncryption::writeVersionEnc()
{
    static const sal_uInt8 mnVersion = 2; // the encrypted version
    mnVersionEnc = mnSeed ^ mnVersion;
    exportString(mrEncryptedData, createHexStringFromDigit(mnVersionEnc));
}
 
sal_uInt8 VBAEncryption::calculateProjKey(const OUString& rProjectKey)
{
    sal_uInt8 nProjKey = 0;
    sal_Int32 n = rProjectKey.getLength();
    const sal_Unicode* pString = rProjectKey.getStr();
    for (sal_Int32 i = 0; i < n; ++i)
    {
        sal_Unicode character = pString[i];
        nProjKey += character;
    }
 
    return nProjKey;
}
 
void VBAEncryption::writeProjKeyEnc()
{
    sal_uInt8 nProjKeyEnc = mnSeed ^ mnProjKey;
    exportString(mrEncryptedData, createHexStringFromDigit(nProjKeyEnc));
    mnUnencryptedByte1 = mnProjKey;
    mnEncryptedByte1 = nProjKeyEnc; // ProjKeyEnc
    mnEncryptedByte2 = mnVersionEnc; // VersionEnc
}
 
void VBAEncryption::writeIgnoredEnc()
{
    mnIgnoredLength = (mnSeed & 6) / 2;
    for(sal_Int32 i = 1; i <= mnIgnoredLength; ++i)
    {
        sal_uInt8 nTempValue = 0xBE; // Any value can be assigned here
        sal_uInt8 nByteEnc = nTempValue ^ (mnEncryptedByte2 + mnUnencryptedByte1);
        exportString(mrEncryptedData, createHexStringFromDigit(nByteEnc));
        mnEncryptedByte2 = mnEncryptedByte1;
        mnEncryptedByte1 = nByteEnc;
        mnUnencryptedByte1 = nTempValue;
    }
}
 
void VBAEncryption::writeDataLengthEnc()
{
    sal_uInt16 temp = mnLength;
    for(sal_Int8 i = 0; i < 4; ++i)
    {
        sal_uInt8 nByte = temp & 0xFF;
        sal_uInt8 nByteEnc = nByte ^ (mnEncryptedByte2 + mnUnencryptedByte1);
        exportString(mrEncryptedData, createHexStringFromDigit(nByteEnc));
        mnEncryptedByte2 = mnEncryptedByte1;
        mnEncryptedByte1 = nByteEnc;
        mnUnencryptedByte1 = nByte;
        temp >>= 8;
    }
}
 
void VBAEncryption::writeDataEnc()
{
    for(sal_Int16 i = 0; i < mnLength; i++)
    {
        sal_uInt8 nByteEnc = mpData[i] ^ (mnEncryptedByte2 + mnUnencryptedByte1);
        exportString(mrEncryptedData, createHexStringFromDigit(nByteEnc));
        mnEncryptedByte2 = mnEncryptedByte1;
        mnEncryptedByte1 = nByteEnc;
        mnUnencryptedByte1 = mpData[i];
    }
}
 
void VBAEncryption::write()
{
    writeSeed();
    writeVersionEnc();
    writeProjKeyEnc();
    writeIgnoredEnc();
    writeDataLengthEnc();
    writeDataEnc();
}
 
#endif
 
VbaExport::VbaExport(css::uno::Reference<css::frame::XModel> const & xModel):
    mxModel(xModel)
{
}
 
namespace {
 
// section 2.3.4.2.1.1
void writePROJECTSYSKIND(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0001); // id
    rStrm.WriteUInt32(0x00000004); // size
    rStrm.WriteUInt32(0x00000001); // SysKind, hard coded to 32-bin windows for now
}
 
// section 2.3.4.2.1.2
void writePROJECTLCID(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0002); // id
    rStrm.WriteUInt32(0x00000004); // size
    rStrm.WriteUInt32(0x00000409); // Lcid
}
 
// section 2.3.4.2.1.3
void writePROJECTLCIDINVOKE(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0014); // id
    rStrm.WriteUInt32(0x00000004); // size
    rStrm.WriteUInt32(0x00000409); // LcidInvoke
}
 
// section 2.3.4.2.1.4
void writePROJECTCODEPAGE(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0003); // id
    rStrm.WriteUInt32(0x00000002); // size
    rStrm.WriteUInt16(CODEPAGE_MS); // CodePage
}
 
//section 2.3.4.2.1.5
void writePROJECTNAME(SvStream& rStrm, const OUString& name)
{
    rStrm.WriteUInt16(0x0004); // id
    sal_uInt32 sizeOfProjectName = name.getLength();
    rStrm.WriteUInt32(sizeOfProjectName); // sizeOfProjectName
    exportString(rStrm, name); // ProjectName
}
 
//section 2.3.4.2.1.6
void writePROJECTDOCSTRING(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0005); // id
    rStrm.WriteUInt32(0x00000000); // sizeOfDocString
    rStrm.WriteUInt16(0x0040); // Reserved
    rStrm.WriteUInt32(0x00000000); // sizeOfDocStringUnicode, MUST be even
}
 
//section 2.3.4.2.1.7
void writePROJECTHELPFILEPATH(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0006); // id
    rStrm.WriteUInt32(0x00000000); // sizeOfHelpFile1
    rStrm.WriteUInt16(0x003D); // Reserved
    rStrm.WriteUInt32(0x00000000); // sizeOfHelpFile2
}
 
//section 2.3.4.2.1.8
void writePROJECTHELPCONTEXT(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0007); // id
    rStrm.WriteUInt32(0x00000004); // size
    rStrm.WriteUInt32(0x00000000); // HelpContext
}
 
//section 2.3.4.2.1.9
void writePROJECTLIBFLAGS(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0008); // id
    rStrm.WriteUInt32(0x00000004); // size
    rStrm.WriteUInt32(0x00000000); // ProjectLibFlags
}
 
//section 2.3.4.2.1.10
void writePROJECTVERSION(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0009); // id
    rStrm.WriteUInt32(0x00000004); // Reserved
    rStrm.WriteUInt32(1467127224); // VersionMajor // TODO: where is this magic number coming from
    rStrm.WriteUInt16(5); // VersionMinor // TODO: where is this magic number coming from
}
 
//section 2.3.4.2.1.11
void writePROJECTCONSTANTS(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x000C); // id
    rStrm.WriteUInt32(0x00000000); // sizeOfConstants
    rStrm.WriteUInt16(0x003C); // Reserved
    rStrm.WriteUInt32(0x00000000); // sizeOfConstantsUnicode
}
 
// section 2.3.4.2.1
void writePROJECTINFORMATION(SvStream& rStrm, const OUString& projectName)
{
    writePROJECTSYSKIND(rStrm);
    writePROJECTLCID(rStrm);
    writePROJECTLCIDINVOKE(rStrm);
    writePROJECTCODEPAGE(rStrm);
    writePROJECTNAME(rStrm, projectName);
    writePROJECTDOCSTRING(rStrm);
    writePROJECTHELPFILEPATH(rStrm);
    writePROJECTHELPCONTEXT(rStrm);
    writePROJECTLIBFLAGS(rStrm);
    writePROJECTVERSION(rStrm);
    writePROJECTCONSTANTS(rStrm);
}
 
// section 2.3.4.2.2.2
void writeREFERENCENAME(SvStream& rStrm, const OUString& name)
{
    rStrm.WriteUInt16(0x0016); // id
    sal_Int32 size = name.getLength();
    rStrm.WriteUInt32(size); // sizeOfName
    exportString(rStrm, name); // name
    rStrm.WriteUInt16(0x003E); // reserved
    sal_Int32 unicodesize = size * 2;
    rStrm.WriteUInt32(unicodesize); // sizeOfNameUnicode
    exportUTF16String(rStrm, name); // nameUnicode
}
 
// section 2.3.4.2.2.5
void writeREFERENCEREGISTERED(SvStream& rStrm, const OUString& libid)
{
    rStrm.WriteUInt16(0x000D); // id
    sal_Int32 sizeOfLibid = libid.getLength();
    sal_Int32 size = sizeOfLibid + 10; // size of Libid, sizeOfLibid(4 bytes), reserved 1(4 bytes) and reserved 2(2 bytes)
    rStrm.WriteUInt32(size); // size
    rStrm.WriteUInt32(sizeOfLibid); // sizeOfLibid
    exportString(rStrm, libid); // Libid
    rStrm.WriteUInt32(0x00000000); // reserved 1
    rStrm.WriteUInt16(0x0000); // reserved 2
}
 
// section 2.3.4.2.2.1
void writeREFERENCE(SvStream& rStrm, const OUString& name, const OUString& libid)
{
    writeREFERENCENAME(rStrm, name);
    writeREFERENCEREGISTERED(rStrm, libid);
}
 
// section 2.3.4.2.2
void writePROJECTREFERENCES(SvStream& rStrm)
{
    // TODO: find out where these references are coming from
    writeREFERENCE(rStrm, "stdole", "*\\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\\Windows\\SysWOW64\\stdole2.tlb#OLE Automation");
    writeREFERENCE(rStrm, "Office", "*\\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.0#0#C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\OFFICE14\\MSO.DLL#Microsoft Office 14.0 Object Library");
}
 
// section 2.3.4.2.3.1
void writePROJECTCOOKIE(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0013); // id
    rStrm.WriteUInt32(0x00000002); // size
    rStrm.WriteUInt16(0xFFFF); // cookie
}
 
// section 2.3.4.2.3.2.1
void writeMODULENAME(SvStream& rStrm, const OUString& name)
{
    rStrm.WriteUInt16(0x0019); // id
    sal_Int32 n = name.getLength(); // sizeOfModuleName
    rStrm.WriteUInt32(n);
    exportString(rStrm, name); // ModuleName
}
 
// section 2.3.4.2.3.2.2
void writeMODULENAMEUNICODE(SvStream& rStrm, const OUString& name)
{
    rStrm.WriteUInt16(0x0047); // id
    sal_Int32 n = name.getLength() * 2; // sizeOfModuleNameUnicode // TODO: better calculation for unicode string length
    rStrm.WriteUInt32(n);
    exportUTF16String(rStrm, name); // ModuleNameUnicode
}
 
// section 2.3.4.2.3.2.3
void writeMODULESTREAMNAME(SvStream& rStrm, const OUString& streamName)
{
    rStrm.WriteUInt16(0x001A); // id
    sal_Int32 n = streamName.getLength(); // sizeOfStreamName
    rStrm.WriteUInt32(n);
    exportString(rStrm, streamName); // StreamName
    rStrm.WriteUInt16(0x0032); // reserved
    rStrm.WriteUInt32(n * 2); // sizeOfStreamNameUnicode // TODO: better calculation for unicode string length
    exportUTF16String(rStrm, streamName); // StreamNameUnicode
}
 
// section 2.3.4.2.3.2.4
void writeMODULEDOCSTRING(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x001C); // id
    rStrm.WriteUInt32(0x00000000); // sizeOfDocString
    rStrm.WriteUInt16(0x0048); // reserved
    rStrm.WriteUInt32(0x00000000); // sizeOfDocStringUnicode
}
 
// section 2.3.4.2.3.2.5
void writeMODULEOFFSET(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x0031); // id
    rStrm.WriteUInt32(0x00000004); // sizeOfTextOffset
    rStrm.WriteUInt32(0x00000000); // TextOffset
}
 
// section 2.3.4.2.3.2.6
void writeMODULEHELPCONTEXT(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x001E); // id
    rStrm.WriteUInt32(0x00000004); // sizeOfHelpContext
    rStrm.WriteUInt32(0x00000000); // HelpContext
}
 
// section 2.3.4.2.3.2.7
void writeMODULECOOKIE(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x002C); // id
    rStrm.WriteUInt32(0x00000002); // sizeOfHelpContext
    rStrm.WriteUInt16(0xFFFF); // HelpContext
}
 
// section 2.3.4.2.3.2.8
void writeMODULETYPE(SvStream& rStrm, const sal_uInt16 type)
{
    if(type == 1)
        rStrm.WriteUInt16(0x0021); // id for a procedural module
    else
        rStrm.WriteUInt16(0x0022); // id for document, class or design module
    rStrm.WriteUInt32(0x00000000); // reserved
}
 
// section 2.3.4.2.3.2
void writePROJECTMODULE(SvStream& rStrm, const OUString& name, const sal_uInt16 type)
{
    writeMODULENAME(rStrm, name);
    writeMODULENAMEUNICODE(rStrm, name);
    writeMODULESTREAMNAME(rStrm, name);
    writeMODULEDOCSTRING(rStrm);
    writeMODULEOFFSET(rStrm);
    writeMODULEHELPCONTEXT(rStrm);
    writeMODULECOOKIE(rStrm);
    writeMODULETYPE(rStrm, type);
    rStrm.WriteUInt16(0x002B); // terminator
    rStrm.WriteUInt32(0x00000000); // reserved
}
 
// section 2.3.4.2.3
void writePROJECTMODULES(SvStream& rStrm, const css::uno::Reference<css::container::XNameContainer>& xNameContainer, const std::vector<sal_Int32>& rLibrayMap)
{
    css::uno::Sequence<OUString> aElementNames = xNameContainer->getElementNames();
    sal_Int32 n = aElementNames.getLength();
    css::uno::Reference<css::script::vba::XVBAModuleInfo> xModuleInfo(xNameContainer, css::uno::UNO_QUERY);
    assert(xModuleInfo.is());
 
    // TODO: this whole part is document specific
    rStrm.WriteUInt16(0x000F); // id
    rStrm.WriteUInt32(0x00000002); // size of Count
    sal_Int16 count = n; // Number of modules // TODO: this is dependent on the document
    rStrm.WriteUInt16(count); // Count
    writePROJECTCOOKIE(rStrm);
 
    for (sal_Int32 i = 0; i < n; ++i)
    {
        const OUString& rModuleName = aElementNames[rLibrayMap[i]];
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(rModuleName);
        writePROJECTMODULE(rStrm, rModuleName, aModuleInfo.ModuleType);
    }
}
 
// section 2.3.4.2
void exportDirStream(SvStream& rStrm, const css::uno::Reference<css::container::XNameContainer>& xNameContainer, const std::vector<sal_Int32>& rLibraryMap, const OUString& projectName)
{
    SvMemoryStream aDirStream(4096, 4096);
 
    writePROJECTINFORMATION(aDirStream, projectName);
    writePROJECTREFERENCES(aDirStream);
    writePROJECTMODULES(aDirStream, xNameContainer, rLibraryMap);
    aDirStream.WriteUInt16(0x0010); // terminator
    aDirStream.WriteUInt32(0x00000000); // reserved
 
    aDirStream.Seek(0);
 
#if VBA_EXPORT_DEBUG
    const OUString aDirFileName("/tmp/vba_dir_out.bin");
    SvFileStream aDirStreamDebug(aDirFileName, StreamMode::READWRITE);
 
    aDirStreamDebug.WriteStream(aDirStream);
    aDirStream.Seek(0);
#endif
 
    // the stream for the compression
    SvMemoryStream aMemoryStream(4096, 4096);
    aMemoryStream.WriteStream(aDirStream);
 
    VBACompression aCompression(rStrm, aDirStream);
    aCompression.write();
}
 
// section 2.3.4.3 Module Stream
void exportModuleStream(SvStream& rStrm, const OUString& rSourceCode, const OUString& aElementName, css::script::ModuleInfo const & rInfo)
{
    SvMemoryStream aModuleStream(4096, 4096);
 
    exportString(aModuleStream, "Attribute VB_Name = \"" + aElementName + "\"\r\n");
    if (rInfo.ModuleType == 4)
    {
        if (isWorkbook(rInfo.ModuleObject))
            exportString(aModuleStream, "Attribute VB_Base = \"0{00020819-0000-0000-C000-000000000046}\"\r\n");
        else
            exportString(aModuleStream, "Attribute VB_Base = \"0{00020820-0000-0000-C000-000000000046}\"\r\n");
 
        exportString(aModuleStream, "Attribute VB_GlobalNameSpace = False\r\n");
        exportString(aModuleStream, "Attribute VB_Creatable = False\r\n");
        exportString(aModuleStream, "Attribute VB_PredeclaredId = True\r\n");
        exportString(aModuleStream, "Attribute VB_Exposed = True\r\n");
        exportString(aModuleStream, "Attribute VB_TemplateDerived = False\r\n");
        exportString(aModuleStream, "Attribute VB_Customizable = True\r\n");
    }
    OUString aSourceCode = rSourceCode.replaceFirst("Option VBASupport 1\n", "");
    const sal_Int32 nPos = aSourceCode.indexOf("Rem Attribute VBA_ModuleType=");
    const sal_Int32 nEndPos = nPos != -1 ? aSourceCode.indexOf("\n", nPos) : -1;
    if (nPos != -1 && nEndPos != -1)
        aSourceCode = aSourceCode.replaceAt(nPos, nEndPos - nPos+1, "");
    aSourceCode = aSourceCode.replaceAll("\n", "\r\n");
    exportString(aModuleStream, aSourceCode);
    aModuleStream.Seek(0);
 
#if VBA_EXPORT_DEBUG
    OUString aModuleFileName("/tmp/vba_" + aElementName + "_out.bin");
    SvFileStream aModuleStreamDebug(aModuleFileName, StreamMode::READWRITE);
    aModuleStreamDebug.WriteStream(aModuleStream);
    aModuleStream.Seek(0);
#endif
 
    // the stream for the compression
    SvMemoryStream aMemoryStream(4096, 4096);
    aMemoryStream.WriteStream(aModuleStream);
 
    VBACompression aCompression(rStrm, aModuleStream);
    aCompression.write();
}
 
// section 2.3.4.1 _VBA_PROJECT Stream
void exportVBAProjectStream(SvStream& rStrm)
{
    rStrm.WriteUInt16(0x61CC); // Reserved1
    rStrm.WriteUInt16(0xFFFF); // Version
    rStrm.WriteUInt8(0x00); // Reserved2
    rStrm.WriteUInt16(0x0000); // Undefined
}
 
// section 2.3.1 PROJECT Stream
void exportPROJECTStream(SvStream& rStrm, const css::uno::Reference<css::container::XNameContainer>& xNameContainer,
        const OUString& projectName, const std::vector<sal_Int32>& rLibraryMap)
{
    css::uno::Sequence<OUString> aElementNames = xNameContainer->getElementNames();
    sal_Int32 n = aElementNames.getLength();
    css::uno::Reference<css::script::vba::XVBAModuleInfo> xModuleInfo(xNameContainer, css::uno::UNO_QUERY);
    assert(xModuleInfo.is());
 
    // section 2.3.1.1ProjectProperties
 
    // section 2.3.1.2 ProjectId
    exportString(rStrm, "ID=\"");
    OUString aProjectID
        = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
    exportString(rStrm, aProjectID);
    exportString(rStrm, "\"\r\n");
 
    // section 2.3.1.3 ProjectModule
    for (sal_Int32 i = 0; i < n; ++i)
    {
        const OUString& rModuleName = aElementNames[rLibraryMap[i]];
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(rModuleName);
        if(aModuleInfo.ModuleType == 1)
        {
            exportString(rStrm, "Module=" + rModuleName + "\r\n");
        }
        else if(aModuleInfo.ModuleType == 4)
        {
            exportString(rStrm, "Document=" + rModuleName + "/&H00000000\r\n");
        }
    }
 
    // section 2.3.1.11 ProjectName
    exportString(rStrm, "Name=\"" + projectName + "\"\r\n");
 
    // section 2.3.1.12 ProjectHelpId
    exportString(rStrm, "HelpContextID=\"0\"\r\n");
 
    // section 2.3.1.14 ProjectVersionCompat32
    exportString(rStrm, "VersionCompatible32=\"393222000\"\r\n");
 
    // section 2.3.1.15 ProjectProtectionState
#if VBA_ENCRYPTION
    exportString(rStrm, "CMG=\"");
    SvMemoryStream aProtectedStream(4096, 4096);
    aProtectedStream.WriteUInt32(0x00000000);
    const sal_uInt8* pData = static_cast<const sal_uInt8*>(aProtectedStream.GetData());
    sal_uInt8 nProjKey = VBAEncryption::calculateProjKey(aProjectID);
    VBAEncryption aProtectionState(pData, 4, rStrm, nProjKey);
    aProtectionState.write();
    exportString(rStrm, "\"\r\n");
#else
    exportString(rStrm, "CMG=\"BEBC9256EEAAA8AEA8AEA8AEA8AE\"\r\n");
#endif
 
    // section 2.3.1.16 ProjectPassword
#if VBA_ENCRYPTION
    exportString(rStrm, "DPB=\"");
    aProtectedStream.Seek(0);
    aProtectedStream.WriteUInt8(0x00);
    pData = static_cast<const sal_uInt8*>(aProtectedStream.GetData());
    VBAEncryption aProjectPassword(pData, 1, rStrm, nProjKey);
    aProjectPassword.write();
    exportString(rStrm, "\"\r\n");
#else
    exportString(rStrm, "DPB=\"7C7E5014B0D3B1D3B1D3\"\r\n");
#endif
 
    // section 2.3.1.17 ProjectVisibilityState
#if VBA_ENCRYPTION
    exportString(rStrm, "GC=\"");
    aProtectedStream.Seek(0);
    aProtectedStream.WriteUInt8(0xFF);
    pData = static_cast<const sal_uInt8*>(aProtectedStream.GetData());
    VBAEncryption aVisibilityState(pData, 1, rStrm, nProjKey);
    aVisibilityState.write();
    exportString(rStrm, "\"\r\n\r\n");
#else
    exportString(rStrm, "GC=\"3A3816DAD5DBD5DB2A\"\r\n\r\n");
#endif
 
    // section 2.3.1.18 HostExtenders
    exportString(rStrm, "[Host Extender Info]\r\n"
                        "&H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000\r\n\r\n"
    );
 
    // section 2.3.1.19 ProjectWorkspace
    exportString(rStrm, "[Workspace]\r\n");
    for (sal_Int32 i = 0; i < n; ++i)
    {
        const OUString& rModuleName = aElementNames[rLibraryMap[i]];
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(rModuleName);
        if(aModuleInfo.ModuleType == 1)
        {
            exportString(rStrm,  rModuleName + "=25, 25, 1439, 639, \r\n");
        }
        else
        {
            exportString(rStrm, rModuleName + "=0, 0, 0, 0, C\r\n");
        }
    }
}
 
// section 2.3.3.1 NAMEMAP
void writeNAMEMAP(SvStream& rStrm, const css::uno::Sequence<OUString>& rElementNames,
        const std::vector<sal_Int32>& rLibraryMap)
{
    int n = rElementNames.getLength();
    for(sal_Int32 i = 0; i < n; ++i)
    {
        const OUString& rModuleName = rElementNames[rLibraryMap[i]];
        exportString(rStrm, rModuleName);
        rStrm.WriteUInt8(0x00); // terminator
        exportUTF16String(rStrm, rModuleName);
        rStrm.WriteUInt16(0x0000); // terminator
    }
}
 
// section 2.3.3 PROJECTwm Stream
void exportPROJECTwmStream(SvStream& rStrm, const css::uno::Sequence<OUString>& rElementNames,
        const std::vector<sal_Int32>& rLibraryMap)
{
    writeNAMEMAP(rStrm, rElementNames, rLibraryMap);
    rStrm.WriteUInt16(0x0000); // terminator
}
 
void getCorrectExportOrder(const css::uno::Reference<css::container::XNameContainer>& xNameContainer, std::vector<sal_Int32>& rLibraryMap)
{
    css::uno::Sequence<OUString> aElementNames = xNameContainer->getElementNames();
    sal_Int32 n = aElementNames.getLength();
    css::uno::Reference<css::script::vba::XVBAModuleInfo> xModuleInfo(xNameContainer, css::uno::UNO_QUERY);
 
    sal_Int32 nCurrentId = 0;
    // first all the non-document modules
    for (sal_Int32 i = 0; i < n; ++i)
    {
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(aElementNames[i]);
        if (aModuleInfo.ModuleType != 4)
        {
            rLibraryMap[nCurrentId] = i;
            ++nCurrentId;
        }
    }
 
    sal_Int32 nWorkbookIndex = -1;
    // then possibly the workbook module
    for (sal_Int32 i = 0; i < n; ++i)
    {
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(aElementNames[i]);
        bool bWorkbook = isWorkbook(aModuleInfo.ModuleObject);
        if (bWorkbook)
        {
            nWorkbookIndex = i;
            rLibraryMap[nCurrentId] = i;
            ++nCurrentId;
        }
    }
 
    // then the remaining modules
    for (sal_Int32 i = 0; i < n; ++i)
    {
        if (i == nWorkbookIndex)
            continue;
 
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(aElementNames[i]);
        if (aModuleInfo.ModuleType == 4)
        {
            rLibraryMap[nCurrentId] = i;
            ++nCurrentId;
        }
    }
}
 
}
 
#if VBA_USE_ORIGINAL_WM_STREAM || VBA_USE_ORIGINAL_DIR_STREAM \
    || VBA_USE_ORIGINAL_PROJECT_STREAM || VBA_USE_ORIGINAL_VBA_PROJECT \
    || VBA_USE_ORIGINAL_DIR_STREAM
void addFileStreamToSotStream(const OUString& rPath, SotStorageStream* pStream)
{
    SvFileStream aFileStream(rPath, StreamMode::READWRITE);
    pStream->WriteStream(aFileStream);
}
#endif
 
void VbaExport::exportVBA(SotStorage* pRootStorage)
{
    css::uno::Reference<css::container::XNameContainer> xNameContainer = getBasicLibrary();
    if (!xNameContainer.is()) {
        return;
    }
    css::uno::Sequence<OUString> aElementNames = xNameContainer->getElementNames();
    sal_Int32 n = aElementNames.getLength(); // get the number of modules
    // export the elements in the order MSO expects them
    // we store the index of the
    std::vector<sal_Int32> aLibraryMap(n, 0);
    getCorrectExportOrder(xNameContainer, aLibraryMap);
 
    // start here with the VBA export
    tools::SvRef<SotStorage> xVBAStream = pRootStorage->OpenSotStorage("VBA", StreamMode::READWRITE);
    SotStorageStream* pDirStream = xVBAStream->OpenSotStream("dir", StreamMode::READWRITE);
 
    SotStorageStream* pVBAProjectStream = xVBAStream->OpenSotStream("_VBA_PROJECT", StreamMode::READWRITE);
    SotStorageStream* pPROJECTStream = pRootStorage->OpenSotStream("PROJECT", StreamMode::READWRITE);
    SotStorageStream* pPROJECTwmStream = pRootStorage->OpenSotStream("PROJECTwm", StreamMode::READWRITE);
 
#if VBA_USE_ORIGINAL_WM_STREAM
    OUString aProjectwmPath = "/home/moggi/Documents/testfiles/vba/PROJECTwm";
    addFileStreamToSotStream(aProjectwmPath, pPROJECTwmStream);
#else
    exportPROJECTwmStream(*pPROJECTwmStream, aElementNames, aLibraryMap);
#endif
 
#if VBA_USE_ORIGINAL_DIR_STREAM
    OUString aDirPath = "/home/moggi/Documents/testfiles/vba/VBA/dir";
    addFileStreamToSotStream(aDirPath, pDirStream);
#else
    exportDirStream(*pDirStream, xNameContainer, aLibraryMap, getProjectName());
#endif
 
#if VBA_USE_ORIGINAL_PROJECT_STREAM
    OUString aProjectPath = "/home/moggi/Documents/testfiles/vba/PROJECT";
    addFileStreamToSotStream(aProjectPath, pPROJECTStream);
#else
    exportPROJECTStream(*pPROJECTStream, xNameContainer, getProjectName(), aLibraryMap);
#endif
 
#if VBA_USE_ORIGINAL_VBA_PROJECT
    OUString a_VBA_ProjectPath = "/home/moggi/Documents/testfiles/vba/VBA/_VBA_PROJECT";
    addFileStreamToSotStream(a_VBA_ProjectPath, pVBAProjectStream);
#else
    exportVBAProjectStream(*pVBAProjectStream);
#endif
 
#if VBA_USE_ORIGINAL_DIR_STREAM
    OUString aModule1Path = "/home/moggi/Documents/testfiles/vba/VBA/Module1";
    OUString aSheet1Path = "/home/moggi/Documents/testfiles/vba/VBA/Sheet1";
    OUString aSheet2Path = "/home/moggi/Documents/testfiles/vba/VBA/Sheet2";
    OUString aSheet3Path = "/home/moggi/Documents/testfiles/vba/VBA/Sheet3";
    OUString aWorkbookPath = "/home/moggi/Documents/testfiles/vba/VBA/ThisWorkbook";
    SotStorageStream* pModule1Stream = xVBAStream->OpenSotStream("Module1", StreamMode::READWRITE);
    SotStorageStream* pSheet1Stream = xVBAStream->OpenSotStream("Sheet1", StreamMode::READWRITE);
    SotStorageStream* pSheet2Stream = xVBAStream->OpenSotStream("Sheet2", StreamMode::READWRITE);
    SotStorageStream* pSheet3Stream = xVBAStream->OpenSotStream("Sheet3", StreamMode::READWRITE);
    SotStorageStream* pWorkbookStream = xVBAStream->OpenSotStream("ThisWorkbook", StreamMode::READWRITE);
    addFileStreamToSotStream(aModule1Path, pModule1Stream);
    addFileStreamToSotStream(aSheet1Path, pSheet1Stream);
    addFileStreamToSotStream(aSheet2Path, pSheet2Stream);
    addFileStreamToSotStream(aSheet3Path, pSheet3Stream);
    addFileStreamToSotStream(aWorkbookPath, pWorkbookStream);
 
    pModule1Stream->Commit();
    pSheet1Stream->Commit();
    pSheet2Stream->Commit();
    pSheet3Stream->Commit();
    pWorkbookStream->Commit();
#else
 
    css::uno::Reference<css::script::vba::XVBAModuleInfo> xModuleInfo(xNameContainer, css::uno::UNO_QUERY);
    for (sal_Int32 i = 0; i < n; ++i)
    {
        const OUString& rModuleName = aElementNames[aLibraryMap[i]];
        SotStorageStream* pModuleStream = xVBAStream->OpenSotStream(rModuleName, StreamMode::READWRITE);
        css::uno::Any aCode = xNameContainer->getByName(rModuleName);
        css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(rModuleName);
        OUString aSourceCode;
        aCode >>= aSourceCode;
        exportModuleStream(*pModuleStream, aSourceCode, rModuleName, aModuleInfo);
        pModuleStream->Commit();
    }
 
#endif
 
    pVBAProjectStream->Commit();
 
    pDirStream->Commit();
    xVBAStream->Commit();
    pPROJECTStream->Commit();
    pPROJECTwmStream->Commit();
    pRootStorage->Commit();
}
 
css::uno::Reference<css::script::XLibraryContainer> VbaExport::getLibraryContainer()
{
    oox::PropertySet aDocProp(mxModel);
    css::uno::Reference<css::script::XLibraryContainer> xLibContainer(aDocProp.getAnyProperty(oox::PROP_BasicLibraries), css::uno::UNO_QUERY);
 
    return xLibContainer;
}
 
css::uno::Reference<css::container::XNameContainer> VbaExport::getBasicLibrary()
{
    css::uno::Reference<css::container::XNameContainer> xLibrary;
    try
    {
        css::uno::Reference<css::script::XLibraryContainer> xLibContainer = getLibraryContainer();
        OUString aProjectName = getProjectName();
        xLibrary.set( xLibContainer->getByName(aProjectName), css::uno::UNO_QUERY_THROW );
    }
    catch(...)
    {
    }
 
    return xLibrary;
}
 
bool VbaExport::containsVBAProject()
{
    css::uno::Reference<css::script::XLibraryContainer> xLibContainer = getLibraryContainer();
    if (!xLibContainer.is())
        return false;
 
    css::uno::Reference<css::script::vba::XVBACompatibility> xVbaCompatibility (xLibContainer, css::uno::UNO_QUERY);
    if (!xVbaCompatibility.is())
        return false;
 
    bool bVBACompatibilty = xVbaCompatibility->getVBACompatibilityMode();
 
    return bVBACompatibilty;
}
 
OUString VbaExport::getProjectName()
{
    css::uno::Reference<css::script::vba::XVBACompatibility> xVbaCompatibility(getLibraryContainer(), css::uno::UNO_QUERY);
    if (xVbaCompatibility.is())
        return xVbaCompatibility->getProjectName();
 
    return OUString();
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V507 Pointer to local array 'pCompressedChunkStream' is stored outside the scope of this array. Such a pointer will become invalid.