office-gobmx/oox/source/ole/vbaexport.cxx
Noel Grandin 239ceb3155 loplugin:ostr in oox
Change-Id: I08124ce2da1facbe2e84aa4a7a8e25fec24fa962
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167428
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2024-05-10 09:51:34 +02:00

1194 lines
40 KiB
C++

/* -*- 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 <random>
#include <string_view>
#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/propertyset.hxx>
#include <oox/token/properties.hxx>
#include <sot/storage.hxx>
#include <comphelper/xmltools.hxx>
#include <utility>
#include <rtl/tencinfo.h>
#include <osl/thread.h>
#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, std::u16string_view rString,
const rtl_TextEncoding eTextEncoding)
{
OString aStringCorrectCodepage = OUStringToOString(rString, eTextEncoding);
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)
{
}
static 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,
const rtl_TextEncoding eTextEncoding)
:mpData(pData)
,mnLength(length)
,mrEncryptedData(rEncryptedData)
,mnUnencryptedByte1(0)
,mnEncryptedByte1(0)
,mnEncryptedByte2(0)
,mnProjKey(nProjKey)
,mnIgnoredLength(0)
,mnSeed(0x00)
,mnVersionEnc(0)
,meTextEncoding(eTextEncoding)
{
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), meTextEncoding);
}
void VBAEncryption::writeVersionEnc()
{
static const sal_uInt8 mnVersion = 2; // the encrypted version
mnVersionEnc = mnSeed ^ mnVersion;
exportString(mrEncryptedData, createHexStringFromDigit(mnVersionEnc), meTextEncoding);
}
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), meTextEncoding);
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), meTextEncoding);
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), meTextEncoding);
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), meTextEncoding);
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> xModel):
mxModel(std::move(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, const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x0003); // id
rStrm.WriteUInt32(0x00000002); // size
rStrm.WriteUInt16(rtl_getWindowsCodePageFromTextEncoding(eTextEncoding)); // CodePage
}
//section 2.3.4.2.1.5
void writePROJECTNAME(SvStream& rStrm, std::u16string_view name, const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x0004); // id
size_t sizeOfProjectName = name.size();
rStrm.WriteUInt32(sizeOfProjectName); // sizeOfProjectName
exportString(rStrm, name, eTextEncoding); // 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, std::u16string_view projectName,
const rtl_TextEncoding eTextEncoding)
{
writePROJECTSYSKIND(rStrm);
writePROJECTLCID(rStrm);
writePROJECTLCIDINVOKE(rStrm);
writePROJECTCODEPAGE(rStrm, eTextEncoding);
writePROJECTNAME(rStrm, projectName, eTextEncoding);
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, const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x0016); // id
sal_Int32 size = name.getLength();
rStrm.WriteUInt32(size); // sizeOfName
exportString(rStrm, name, eTextEncoding); // 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, std::u16string_view libid,
const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x000D); // id
size_t sizeOfLibid = libid.size();
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, eTextEncoding); // 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, std::u16string_view libid,
const rtl_TextEncoding eTextEncoding)
{
writeREFERENCENAME(rStrm, name, eTextEncoding);
writeREFERENCEREGISTERED(rStrm, libid, eTextEncoding);
}
// section 2.3.4.2.2
void writePROJECTREFERENCES(SvStream& rStrm, const rtl_TextEncoding eTextEncoding)
{
// TODO: find out where these references are coming from
writeREFERENCE(rStrm, u"stdole"_ustr, u"*\\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\\Windows\\SysWOW64\\stdole2.tlb#OLE Automation", eTextEncoding);
writeREFERENCE(rStrm, u"Office"_ustr, u"*\\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", eTextEncoding);
}
// 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, std::u16string_view name, const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x0019); // id
sal_Int32 n = name.size(); // sizeOfModuleName
rStrm.WriteUInt32(n);
exportString(rStrm, name, eTextEncoding); // 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,
const rtl_TextEncoding eTextEncoding)
{
rStrm.WriteUInt16(0x001A); // id
sal_Int32 n = streamName.getLength(); // sizeOfStreamName
rStrm.WriteUInt32(n);
exportString(rStrm, streamName, eTextEncoding); // 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,
const rtl_TextEncoding eTextEncoding)
{
writeMODULENAME(rStrm, name, eTextEncoding);
writeMODULENAMEUNICODE(rStrm, name);
writeMODULESTREAMNAME(rStrm, name, eTextEncoding);
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>& rLibraryMap,
const rtl_TextEncoding eTextEncoding)
{
const 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[rLibraryMap[i]];
css::script::ModuleInfo aModuleInfo = xModuleInfo->getModuleInfo(rModuleName);
writePROJECTMODULE(rStrm, rModuleName, aModuleInfo.ModuleType, eTextEncoding);
}
}
// 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,
const rtl_TextEncoding eTextEncoding)
{
SvMemoryStream aDirStream(4096, 4096);
writePROJECTINFORMATION(aDirStream, projectName, eTextEncoding);
writePROJECTREFERENCES(aDirStream, eTextEncoding);
writePROJECTMODULES(aDirStream, xNameContainer, rLibraryMap, eTextEncoding);
aDirStream.WriteUInt16(0x0010); // terminator
aDirStream.WriteUInt32(0x00000000); // reserved
#if VBA_EXPORT_DEBUG
static constexpr OUStringLiteral aDirFileName(u"/tmp/vba_dir_out.bin");
SvFileStream aDirStreamDebug(aDirFileName, StreamMode::READWRITE);
aDirStream.Seek(0);
aDirStreamDebug.WriteStream(aDirStream);
#endif
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, const rtl_TextEncoding eTextEncoding)
{
SvMemoryStream aModuleStream(4096, 4096);
exportString(aModuleStream, Concat2View("Attribute VB_Name = \"" + aElementName + "\"\r\n"), eTextEncoding);
if (rInfo.ModuleType == 4)
{
if (isWorkbook(rInfo.ModuleObject))
exportString(aModuleStream, u"Attribute VB_Base = \"0{00020819-0000-0000-C000-000000000046}\"\r\n", eTextEncoding);
else
exportString(aModuleStream, u"Attribute VB_Base = \"0{00020820-0000-0000-C000-000000000046}\"\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_GlobalNameSpace = False\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_Creatable = False\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_PredeclaredId = True\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_Exposed = True\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_TemplateDerived = False\r\n", eTextEncoding);
exportString(aModuleStream, u"Attribute VB_Customizable = True\r\n", eTextEncoding);
}
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, u"");
aSourceCode = aSourceCode.replaceAll("\n", "\r\n");
exportString(aModuleStream, aSourceCode, eTextEncoding);
#if VBA_EXPORT_DEBUG
OUString aModuleFileName("/tmp/vba_" + aElementName + "_out.bin");
SvFileStream aModuleStreamDebug(aModuleFileName, StreamMode::READWRITE);
aModuleStream.Seek(0);
aModuleStreamDebug.WriteStream(aModuleStream);
#endif
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,
const rtl_TextEncoding eTextEncoding)
{
const 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, u"ID=\"", eTextEncoding);
OUString aProjectID
= OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
exportString(rStrm, aProjectID, eTextEncoding);
exportString(rStrm, u"\"\r\n", eTextEncoding);
// 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, Concat2View("Module=" + rModuleName + "\r\n"),
eTextEncoding);
}
else if(aModuleInfo.ModuleType == 4)
{
exportString(rStrm,
Concat2View("Document=" + rModuleName + "/&H00000000\r\n"),
eTextEncoding);
}
}
// section 2.3.1.11 ProjectName
exportString(rStrm, Concat2View("Name=\"" + projectName + "\"\r\n"), eTextEncoding);
// section 2.3.1.12 ProjectHelpId
exportString(rStrm, u"HelpContextID=\"0\"\r\n", eTextEncoding);
// section 2.3.1.14 ProjectVersionCompat32
exportString(rStrm, u"VersionCompatible32=\"393222000\"\r\n", eTextEncoding);
// section 2.3.1.15 ProjectProtectionState
#if VBA_ENCRYPTION
exportString(rStrm, u"CMG=\"", eTextEncoding);
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, eTextEncoding);
aProtectionState.write();
exportString(rStrm, u"\"\r\n", eTextEncoding);
#else
exportString(rStrm, "CMG=\"BEBC9256EEAAA8AEA8AEA8AEA8AE\"\r\n", eTextEncoding);
#endif
// section 2.3.1.16 ProjectPassword
#if VBA_ENCRYPTION
exportString(rStrm, u"DPB=\"", eTextEncoding);
aProtectedStream.Seek(0);
aProtectedStream.WriteUInt8(0x00);
pData = static_cast<const sal_uInt8*>(aProtectedStream.GetData());
VBAEncryption aProjectPassword(pData, 1, rStrm, nProjKey, eTextEncoding);
aProjectPassword.write();
exportString(rStrm, u"\"\r\n", eTextEncoding);
#else
exportString(rStrm, "DPB=\"7C7E5014B0D3B1D3B1D3\"\r\n", eTextEncoding);
#endif
// section 2.3.1.17 ProjectVisibilityState
#if VBA_ENCRYPTION
exportString(rStrm, u"GC=\"", eTextEncoding);
aProtectedStream.Seek(0);
aProtectedStream.WriteUInt8(0xFF);
pData = static_cast<const sal_uInt8*>(aProtectedStream.GetData());
VBAEncryption aVisibilityState(pData, 1, rStrm, nProjKey, eTextEncoding);
aVisibilityState.write();
exportString(rStrm, u"\"\r\n\r\n", eTextEncoding);
#else
exportString(rStrm, "GC=\"3A3816DAD5DBD5DB2A\"\r\n\r\n", eTextEncoding);
#endif
// section 2.3.1.18 HostExtenders
exportString(rStrm,
u"[Host Extender Info]\r\n"
"&H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000\r\n\r\n",
eTextEncoding);
// section 2.3.1.19 ProjectWorkspace
exportString(rStrm, u"[Workspace]\r\n", eTextEncoding);
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, Concat2View(rModuleName + "=25, 25, 1439, 639, \r\n"),
eTextEncoding);
}
else
{
exportString(rStrm, Concat2View(rModuleName + "=0, 0, 0, 0, C\r\n"),
eTextEncoding);
}
}
}
// section 2.3.3.1 NAMEMAP
void writeNAMEMAP(SvStream& rStrm, const css::uno::Sequence<OUString>& rElementNames,
const std::vector<sal_Int32>& rLibraryMap, const rtl_TextEncoding eTextEncoding)
{
int n = rElementNames.getLength();
for(sal_Int32 i = 0; i < n; ++i)
{
const OUString& rModuleName = rElementNames[rLibraryMap[i]];
exportString(rStrm, rModuleName, eTextEncoding);
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, const rtl_TextEncoding eTextEncoding)
{
writeNAMEMAP(rStrm, rElementNames, rLibraryMap, eTextEncoding);
rStrm.WriteUInt16(0x0000); // terminator
}
void getCorrectExportOrder(const css::uno::Reference<css::container::XNameContainer>& xNameContainer, std::vector<sal_Int32>& rLibraryMap)
{
const 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& rStream)
{
SvFileStream aFileStream(rPath, StreamMode::READWRITE);
rStream.WriteStream(aFileStream);
}
#endif
void VbaExport::exportVBA(SotStorage* pRootStorage)
{
css::uno::Reference<css::container::XNameContainer> xNameContainer = getBasicLibrary();
if (!xNameContainer.is()) {
return;
}
const 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
rtl::Reference<SotStorage> xVBAStream = pRootStorage->OpenSotStorage(u"VBA"_ustr, StreamMode::READWRITE);
rtl::Reference<SotStorageStream> pDirStream = xVBAStream->OpenSotStream(u"dir"_ustr, StreamMode::READWRITE);
rtl::Reference<SotStorageStream> pVBAProjectStream = xVBAStream->OpenSotStream(u"_VBA_PROJECT"_ustr, StreamMode::READWRITE);
rtl::Reference<SotStorageStream> pPROJECTStream = pRootStorage->OpenSotStream(u"PROJECT"_ustr, StreamMode::READWRITE);
rtl::Reference<SotStorageStream> pPROJECTwmStream = pRootStorage->OpenSotStream(u"PROJECTwm"_ustr, StreamMode::READWRITE);
const rtl_TextEncoding eTextEncoding = getVBATextEncoding();
#if VBA_USE_ORIGINAL_WM_STREAM
OUString aProjectwmPath = "/home/moggi/Documents/testfiles/vba/PROJECTwm";
addFileStreamToSotStream(aProjectwmPath, *pPROJECTwmStream);
#else
exportPROJECTwmStream(*pPROJECTwmStream, aElementNames, aLibraryMap, eTextEncoding);
#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(), eTextEncoding);
#endif
#if VBA_USE_ORIGINAL_PROJECT_STREAM
OUString aProjectPath = "/home/moggi/Documents/testfiles/vba/PROJECT";
addFileStreamToSotStream(aProjectPath, *pPROJECTStream);
#else
exportPROJECTStream(*pPROJECTStream, xNameContainer, getProjectName(), aLibraryMap,
eTextEncoding);
#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";
tools::SvRef<SotStorageStream> pModule1Stream = xVBAStream->OpenSotStream("Module1", StreamMode::READWRITE);
tools::SvRef<SotStorageStream> pSheet1Stream = xVBAStream->OpenSotStream("Sheet1", StreamMode::READWRITE);
tools::SvRef<SotStorageStream> pSheet2Stream = xVBAStream->OpenSotStream("Sheet2", StreamMode::READWRITE);
tools::SvRef<SotStorageStream> pSheet3Stream = xVBAStream->OpenSotStream("Sheet3", StreamMode::READWRITE);
tools::SvRef<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]];
rtl::Reference<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, eTextEncoding);
pModuleStream->Commit();
}
#endif
pVBAProjectStream->Commit();
pDirStream->Commit();
xVBAStream->Commit();
pPROJECTStream->Commit();
pPROJECTwmStream->Commit();
pRootStorage->Commit();
}
css::uno::Reference<css::script::XLibraryContainer> VbaExport::getLibraryContainer() const
{
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() const
{
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 bVBACompatibility = xVbaCompatibility->getVBACompatibilityMode();
return bVBACompatibility;
}
OUString VbaExport::getProjectName() const
{
css::uno::Reference<css::script::vba::XVBACompatibility> xVbaCompatibility(getLibraryContainer(), css::uno::UNO_QUERY);
if (xVbaCompatibility.is())
return xVbaCompatibility->getProjectName();
return OUString();
}
rtl_TextEncoding VbaExport::getVBATextEncoding() const
{
rtl_TextEncoding aTextEncoding = osl_getThreadTextEncoding();
css::uno::Reference<css::beans::XPropertySet> xProps(getLibraryContainer(),
css::uno::UNO_QUERY);
if (xProps.is())
try
{
xProps->getPropertyValue(u"VBATextEncoding"_ustr) >>= aTextEncoding;
}
catch (const css::uno::Exception&)
{
}
return aTextEncoding;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */