1378 lines
52 KiB
C++
1378 lines
52 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/.
|
|
*
|
|
* 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 <ZipPackageStream.hxx>
|
|
|
|
#include <com/sun/star/beans/PropertyValue.hpp>
|
|
#include <com/sun/star/container/XNameContainer.hpp>
|
|
#include <com/sun/star/packages/NoRawFormatException.hpp>
|
|
#include <com/sun/star/packages/zip/ZipConstants.hpp>
|
|
#include <com/sun/star/embed/StorageFormats.hpp>
|
|
#include <com/sun/star/packages/zip/ZipIOException.hpp>
|
|
#include <com/sun/star/packages/NoEncryptionException.hpp>
|
|
#include <com/sun/star/packages/zip/ZipException.hpp>
|
|
#include <com/sun/star/packages/WrongPasswordException.hpp>
|
|
#include <com/sun/star/io/TempFile.hpp>
|
|
#include <com/sun/star/io/XInputStream.hpp>
|
|
#include <com/sun/star/io/XOutputStream.hpp>
|
|
#include <com/sun/star/io/XStream.hpp>
|
|
#include <com/sun/star/io/XSeekable.hpp>
|
|
#include <com/sun/star/xml/crypto/DigestID.hpp>
|
|
#include <com/sun/star/xml/crypto/CipherID.hpp>
|
|
#include <com/sun/star/xml/crypto/KDFID.hpp>
|
|
|
|
#include <CRC32.hxx>
|
|
#include <ZipOutputEntry.hxx>
|
|
#include <ZipOutputStream.hxx>
|
|
#include <ZipPackage.hxx>
|
|
#include <ZipFile.hxx>
|
|
#include <EncryptedDataHeader.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include "wrapstreamforshare.hxx"
|
|
|
|
#include <comphelper/seekableinput.hxx>
|
|
#include <comphelper/servicehelper.hxx>
|
|
#include <comphelper/storagehelper.hxx>
|
|
#include <cppuhelper/supportsservice.hxx>
|
|
#include <unotools/tempfile.hxx>
|
|
|
|
#include <rtl/random.h>
|
|
#include <sal/log.hxx>
|
|
#include <o3tl/unreachable.hxx>
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
|
|
#include <PackageConstants.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
|
|
using namespace com::sun::star::packages::zip::ZipConstants;
|
|
using namespace com::sun::star::packages::zip;
|
|
using namespace com::sun::star::uno;
|
|
using namespace com::sun::star::lang;
|
|
using namespace com::sun::star;
|
|
using namespace cppu;
|
|
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
#define THROW_WHERE SAL_WHERE
|
|
#else
|
|
#define THROW_WHERE ""
|
|
#endif
|
|
|
|
ZipPackageStream::ZipPackageStream ( ZipPackage & rNewPackage,
|
|
const uno::Reference< XComponentContext >& xContext,
|
|
sal_Int32 nFormat,
|
|
bool bAllowRemoveOnInsert )
|
|
: m_rZipPackage( rNewPackage )
|
|
, m_bToBeCompressed ( true )
|
|
, m_bToBeEncrypted ( false )
|
|
, m_bHaveOwnKey ( false )
|
|
, m_bIsEncrypted ( false )
|
|
, m_nStreamMode( PACKAGE_STREAM_NOTSET )
|
|
, m_nMagicalHackPos( 0 )
|
|
, m_nOwnStreamOrigSize( 0 )
|
|
, m_bHasSeekable( false )
|
|
, m_bCompressedIsSetFromOutside( false )
|
|
, m_bFromManifest( false )
|
|
, m_bUseWinEncoding( false )
|
|
, m_bRawStream( false )
|
|
{
|
|
m_xContext = xContext;
|
|
m_nFormat = nFormat;
|
|
mbAllowRemoveOnInsert = bAllowRemoveOnInsert;
|
|
SetFolder ( false );
|
|
aEntry.nVersion = -1;
|
|
aEntry.nFlag = 0;
|
|
aEntry.nMethod = -1;
|
|
aEntry.nTime = -1;
|
|
aEntry.nCrc = -1;
|
|
aEntry.nCompressedSize = -1;
|
|
aEntry.nSize = -1;
|
|
aEntry.nOffset = -1;
|
|
aEntry.nPathLen = -1;
|
|
aEntry.nExtraLen = -1;
|
|
}
|
|
|
|
ZipPackageStream::~ZipPackageStream()
|
|
{
|
|
}
|
|
|
|
void ZipPackageStream::setZipEntryOnLoading( const ZipEntry &rInEntry )
|
|
{
|
|
aEntry.nVersion = rInEntry.nVersion;
|
|
aEntry.nFlag = rInEntry.nFlag;
|
|
aEntry.nMethod = rInEntry.nMethod;
|
|
aEntry.nTime = rInEntry.nTime;
|
|
aEntry.nCrc = rInEntry.nCrc;
|
|
aEntry.nCompressedSize = rInEntry.nCompressedSize;
|
|
aEntry.nSize = rInEntry.nSize;
|
|
aEntry.nOffset = rInEntry.nOffset;
|
|
aEntry.sPath = rInEntry.sPath;
|
|
aEntry.nPathLen = rInEntry.nPathLen;
|
|
aEntry.nExtraLen = rInEntry.nExtraLen;
|
|
|
|
if ( aEntry.nMethod == STORED )
|
|
m_bToBeCompressed = false;
|
|
|
|
// this is called first, parseManifest may overwrite it if it's encrypted
|
|
assert(m_nOwnStreamOrigSize == 0);
|
|
m_nOwnStreamOrigSize = aEntry.nSize;
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > const & ZipPackageStream::GetOwnSeekStream()
|
|
{
|
|
if ( !m_bHasSeekable && m_xStream.is() )
|
|
{
|
|
// The package component requires that every stream either be FROM a package or it must support XSeekable!
|
|
// The only exception is a nonseekable stream that is provided only for storing, if such a stream
|
|
// is accessed before commit it MUST be wrapped.
|
|
// Wrap the stream in case it is not seekable
|
|
m_xStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xStream, m_xContext );
|
|
uno::Reference< io::XSeekable > xSeek( m_xStream, UNO_QUERY_THROW );
|
|
|
|
m_bHasSeekable = true;
|
|
}
|
|
|
|
return m_xStream;
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > ZipPackageStream::GetRawEncrStreamNoHeaderCopy()
|
|
{
|
|
if ( m_nStreamMode != PACKAGE_STREAM_RAW || !GetOwnSeekStream().is() )
|
|
throw io::IOException(THROW_WHERE );
|
|
|
|
if ( m_xBaseEncryptionData.is() )
|
|
throw ZipIOException(THROW_WHERE "Encrypted stream without encryption data!" );
|
|
|
|
uno::Reference< io::XSeekable > xSeek( GetOwnSeekStream(), UNO_QUERY );
|
|
if ( !xSeek.is() )
|
|
throw ZipIOException(THROW_WHERE "The stream must be seekable!" );
|
|
|
|
// skip header
|
|
xSeek->seek( n_ConstHeaderSize + m_xBaseEncryptionData->m_aInitVector.getLength() +
|
|
m_xBaseEncryptionData->m_aSalt.getLength() + m_xBaseEncryptionData->m_aDigest.getLength() );
|
|
|
|
// create temporary stream
|
|
rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService;
|
|
uno::Reference < io::XInputStream > xTempIn = xTempFile->getInputStream();
|
|
|
|
// copy the raw stream to the temporary file starting from the current position
|
|
::comphelper::OStorageHelper::CopyInputToOutput( GetOwnSeekStream(), xTempFile );
|
|
xTempFile->closeOutput();
|
|
xTempFile->seek( 0 );
|
|
|
|
return xTempIn;
|
|
}
|
|
|
|
sal_Int32 ZipPackageStream::GetEncryptionAlgorithm() const
|
|
{
|
|
return m_oImportedAlgorithms
|
|
? m_oImportedAlgorithms->nImportedEncryptionAlgorithm
|
|
: m_rZipPackage.GetEncAlgID();
|
|
}
|
|
|
|
sal_Int32 ZipPackageStream::GetIVSize() const
|
|
{
|
|
switch (GetEncryptionAlgorithm())
|
|
{
|
|
case css::xml::crypto::CipherID::BLOWFISH_CFB_8:
|
|
return 8;
|
|
case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING:
|
|
return 16;
|
|
case css::xml::crypto::CipherID::AES_GCM_W3C:
|
|
return 12;
|
|
default:
|
|
O3TL_UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
::rtl::Reference<EncryptionData> ZipPackageStream::GetEncryptionData(Bugs const bugs)
|
|
{
|
|
::rtl::Reference< EncryptionData > xResult;
|
|
if ( m_xBaseEncryptionData.is() )
|
|
xResult = new EncryptionData(
|
|
*m_xBaseEncryptionData,
|
|
GetEncryptionKey(bugs),
|
|
GetEncryptionAlgorithm(),
|
|
m_oImportedAlgorithms ? m_oImportedAlgorithms->oImportedChecksumAlgorithm : m_rZipPackage.GetChecksumAlgID(),
|
|
m_oImportedAlgorithms ? m_oImportedAlgorithms->nImportedDerivedKeySize : m_rZipPackage.GetDefaultDerivedKeySize(),
|
|
GetStartKeyGenID(),
|
|
bugs != Bugs::None);
|
|
|
|
return xResult;
|
|
}
|
|
|
|
uno::Sequence<sal_Int8> ZipPackageStream::GetEncryptionKey(Bugs const bugs)
|
|
{
|
|
uno::Sequence< sal_Int8 > aResult;
|
|
sal_Int32 nKeyGenID = GetStartKeyGenID();
|
|
bool const bUseWinEncoding = (bugs == Bugs::WinEncodingWrongSHA1 || m_bUseWinEncoding);
|
|
|
|
if ( m_bHaveOwnKey && m_aStorageEncryptionKeys.hasElements() )
|
|
{
|
|
OUString aNameToFind;
|
|
if ( nKeyGenID == xml::crypto::DigestID::SHA256 )
|
|
aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8;
|
|
else if ( nKeyGenID == xml::crypto::DigestID::SHA1 )
|
|
{
|
|
aNameToFind = bUseWinEncoding
|
|
? PACKAGE_ENCRYPTIONDATA_SHA1MS1252
|
|
: (bugs == Bugs::WrongSHA1)
|
|
? PACKAGE_ENCRYPTIONDATA_SHA1UTF8
|
|
: PACKAGE_ENCRYPTIONDATA_SHA1CORRECT;
|
|
}
|
|
else
|
|
throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" );
|
|
|
|
for (const auto& rKey : m_aStorageEncryptionKeys)
|
|
if ( rKey.Name == aNameToFind )
|
|
rKey.Value >>= aResult;
|
|
|
|
// empty keys are not allowed here
|
|
// so it is not important whether there is no key, or the key is empty, it is an error
|
|
if ( !aResult.hasElements() )
|
|
throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" );
|
|
}
|
|
else
|
|
aResult = m_aEncryptionKey;
|
|
|
|
if ( !aResult.hasElements() || !m_bHaveOwnKey )
|
|
aResult = m_rZipPackage.GetEncryptionKey();
|
|
|
|
return aResult;
|
|
}
|
|
|
|
sal_Int32 ZipPackageStream::GetStartKeyGenID() const
|
|
{
|
|
// generally should all the streams use the same Start Key
|
|
// but if raw copy without password takes place, we should preserve the imported algorithm
|
|
return m_oImportedAlgorithms
|
|
? m_oImportedAlgorithms->nImportedStartKeyAlgorithm
|
|
: m_rZipPackage.GetStartKeyGenID();
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > ZipPackageStream::TryToGetRawFromDataStream( bool bAddHeaderForEncr )
|
|
{
|
|
if ( m_nStreamMode != PACKAGE_STREAM_DATA || !GetOwnSeekStream().is() || ( bAddHeaderForEncr && !m_bToBeEncrypted ) )
|
|
throw packages::NoEncryptionException(THROW_WHERE );
|
|
|
|
Sequence< sal_Int8 > aKey;
|
|
|
|
if ( m_bToBeEncrypted )
|
|
{
|
|
aKey = GetEncryptionKey();
|
|
if ( !aKey.hasElements() )
|
|
throw packages::NoEncryptionException(THROW_WHERE );
|
|
}
|
|
|
|
try
|
|
{
|
|
// create temporary file
|
|
uno::Reference < io::XStream > xTempStream(new utl::TempFileFastService);
|
|
|
|
// create a package based on it
|
|
rtl::Reference<ZipPackage> pPackage = new ZipPackage( m_xContext );
|
|
|
|
Sequence< Any > aArgs{ Any(xTempStream) };
|
|
pPackage->initialize( aArgs );
|
|
|
|
// create a new package stream
|
|
uno::Reference< XDataSinkEncrSupport > xNewPackStream( pPackage->createInstance(), UNO_QUERY_THROW );
|
|
xNewPackStream->setDataStream(
|
|
new WrapStreamForShare(GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef()));
|
|
|
|
uno::Reference< XPropertySet > xNewPSProps( xNewPackStream, UNO_QUERY_THROW );
|
|
|
|
// copy all the properties of this stream to the new stream
|
|
xNewPSProps->setPropertyValue(u"MediaType"_ustr, Any( msMediaType ) );
|
|
xNewPSProps->setPropertyValue(u"Compressed"_ustr, Any( m_bToBeCompressed ) );
|
|
if ( m_bToBeEncrypted )
|
|
{
|
|
xNewPSProps->setPropertyValue(ENCRYPTION_KEY_PROPERTY, Any( aKey ) );
|
|
xNewPSProps->setPropertyValue(u"Encrypted"_ustr, Any( true ) );
|
|
}
|
|
|
|
// insert a new stream in the package
|
|
Any aRoot = pPackage->getByHierarchicalName(u"/"_ustr);
|
|
auto xRootNameContainer = aRoot.queryThrow<container::XNameContainer>();
|
|
|
|
xRootNameContainer->insertByName(u"dummy"_ustr, Any( xNewPackStream ) );
|
|
|
|
// commit the temporary package
|
|
pPackage->commitChanges();
|
|
|
|
// get raw stream from the temporary package
|
|
uno::Reference< io::XInputStream > xInRaw;
|
|
if ( bAddHeaderForEncr )
|
|
xInRaw = xNewPackStream->getRawStream();
|
|
else
|
|
xInRaw = xNewPackStream->getPlainRawStream();
|
|
|
|
// create another temporary file
|
|
rtl::Reference < utl::TempFileFastService > xTempOut = new utl::TempFileFastService;
|
|
uno::Reference < io::XInputStream > xTempIn( xTempOut );
|
|
|
|
// copy the raw stream to the temporary file
|
|
::comphelper::OStorageHelper::CopyInputToOutput( xInRaw, xTempOut );
|
|
xTempOut->closeOutput();
|
|
xTempOut->seek( 0 );
|
|
|
|
// close raw stream, package stream and folder
|
|
xInRaw.clear();
|
|
xNewPSProps.clear();
|
|
xNewPackStream.clear();
|
|
xRootNameContainer.clear();
|
|
|
|
// return the stream representing the first temporary file
|
|
return xTempIn;
|
|
}
|
|
catch ( RuntimeException& )
|
|
{
|
|
throw;
|
|
}
|
|
catch ( Exception& )
|
|
{
|
|
}
|
|
|
|
throw io::IOException(THROW_WHERE );
|
|
}
|
|
|
|
// presumably the purpose of this is to transfer encrypted streams between
|
|
// storages, needed for password-protected macros in documents, which is
|
|
// tragically a feature that exists
|
|
bool ZipPackageStream::ParsePackageRawStream()
|
|
{
|
|
OSL_ENSURE( GetOwnSeekStream().is(), "A stream must be provided!" );
|
|
|
|
if ( !GetOwnSeekStream().is() )
|
|
return false;
|
|
|
|
bool bOk = false;
|
|
|
|
::rtl::Reference< BaseEncryptionData > xTempEncrData;
|
|
Sequence < sal_Int8 > aHeader ( 4 );
|
|
|
|
try
|
|
{
|
|
if ( GetOwnSeekStream()->readBytes ( aHeader, 4 ) == 4 )
|
|
{
|
|
const sal_Int8 *pHeader = aHeader.getConstArray();
|
|
sal_uInt32 nHeader = ( pHeader [0] & 0xFF ) |
|
|
( pHeader [1] & 0xFF ) << 8 |
|
|
( pHeader [2] & 0xFF ) << 16 |
|
|
( pHeader [3] & 0xFF ) << 24;
|
|
if ( nHeader == n_ConstHeader )
|
|
{
|
|
// this is one of our god-awful, but extremely devious hacks, everyone cheer
|
|
xTempEncrData = new BaseEncryptionData;
|
|
|
|
OUString aMediaType;
|
|
sal_Int32 nEncAlgorithm = 0;
|
|
sal_Int32 nChecksumAlgorithm = 0;
|
|
sal_Int32 nDerivedKeySize = 0;
|
|
sal_Int32 nStartKeyGenID = 0;
|
|
sal_Int32 nMagHackSize = 0;
|
|
if ( ZipFile::StaticFillData( xTempEncrData, nEncAlgorithm, nChecksumAlgorithm, nDerivedKeySize, nStartKeyGenID, nMagHackSize, aMediaType, GetOwnSeekStream() ) )
|
|
{
|
|
// We'll want to skip the data we've just read, so calculate how much we just read
|
|
// and remember it
|
|
m_nMagicalHackPos = n_ConstHeaderSize + xTempEncrData->m_aSalt.getLength()
|
|
+ xTempEncrData->m_aInitVector.getLength()
|
|
+ xTempEncrData->m_aDigest.getLength()
|
|
+ aMediaType.getLength() * sizeof( sal_Unicode );
|
|
m_oImportedAlgorithms.emplace(ImportedAlgorithms{
|
|
.nImportedStartKeyAlgorithm = nStartKeyGenID,
|
|
.nImportedEncryptionAlgorithm = nEncAlgorithm,
|
|
.oImportedChecksumAlgorithm = nChecksumAlgorithm == 0
|
|
? ::std::optional<sal_Int32>{}
|
|
: ::std::optional<sal_Int32>{nChecksumAlgorithm},
|
|
.nImportedDerivedKeySize = nDerivedKeySize,
|
|
});
|
|
m_nOwnStreamOrigSize = nMagHackSize;
|
|
msMediaType = aMediaType;
|
|
|
|
bOk = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
}
|
|
|
|
if ( !bOk )
|
|
{
|
|
// the provided stream is not a raw stream
|
|
return false;
|
|
}
|
|
|
|
m_xBaseEncryptionData = std::move(xTempEncrData);
|
|
SetIsEncrypted ( true );
|
|
// it's already compressed and encrypted
|
|
m_bToBeEncrypted = m_bToBeCompressed = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ImplSetStoredData( ZipEntry & rEntry, uno::Reference< io::XInputStream> const & rStream )
|
|
{
|
|
// It's very annoying that we have to do this, but lots of zip packages
|
|
// don't allow data descriptors for STORED streams, meaning we have to
|
|
// know the size and CRC32 of uncompressed streams before we actually
|
|
// write them !
|
|
CRC32 aCRC32;
|
|
rEntry.nMethod = STORED;
|
|
rEntry.nCompressedSize = rEntry.nSize = aCRC32.updateStream ( rStream );
|
|
rEntry.nCrc = aCRC32.getValue();
|
|
}
|
|
|
|
bool ZipPackageStream::saveChild(
|
|
const OUString &rPath,
|
|
std::vector < uno::Sequence < beans::PropertyValue > > &rManList,
|
|
ZipOutputStream & rZipOut,
|
|
const uno::Sequence < sal_Int8 >& rEncryptionKey,
|
|
::std::optional<sal_Int32> const oPBKDF2IterationCount,
|
|
::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args)
|
|
{
|
|
static constexpr OUString sDigestProperty (u"Digest"_ustr);
|
|
static constexpr OUString sEncryptionAlgProperty (u"EncryptionAlgorithm"_ustr);
|
|
static constexpr OUString sStartKeyAlgProperty (u"StartKeyAlgorithm"_ustr);
|
|
static constexpr OUString sDigestAlgProperty (u"DigestAlgorithm"_ustr);
|
|
static constexpr OUString sDerivedKeySizeProperty (u"DerivedKeySize"_ustr);
|
|
|
|
uno::Sequence < beans::PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST);
|
|
|
|
// In case the entry we are reading is also the entry we are writing, we will
|
|
// store the ZipEntry data in pTempEntry
|
|
|
|
// if pTempEntry is necessary, it will be released and passed to the ZipOutputStream
|
|
// and be deleted in the ZipOutputStream destructor
|
|
std::unique_ptr < ZipEntry > pAutoTempEntry ( new ZipEntry(aEntry) );
|
|
ZipEntry* pTempEntry = pAutoTempEntry.get();
|
|
|
|
pTempEntry->sPath = rPath;
|
|
pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( pTempEntry->sPath, RTL_TEXTENCODING_UTF8 ).getLength() );
|
|
|
|
const bool bToBeEncrypted = m_bToBeEncrypted && (rEncryptionKey.hasElements() || m_bHaveOwnKey);
|
|
const bool bToBeCompressed = bToBeEncrypted || m_bToBeCompressed;
|
|
|
|
auto pPropSet = aPropSet.getArray();
|
|
pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType";
|
|
pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType( );
|
|
pPropSet[PKG_MNFST_VERSION].Name = "Version";
|
|
pPropSet[PKG_MNFST_VERSION].Value <<= OUString(); // no version is stored for streams currently
|
|
pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath";
|
|
pPropSet[PKG_MNFST_FULLPATH].Value <<= pTempEntry->sPath;
|
|
|
|
OSL_ENSURE( m_nStreamMode != PACKAGE_STREAM_NOTSET, "Unacceptable ZipPackageStream mode!" );
|
|
|
|
m_bRawStream = false;
|
|
if ( m_nStreamMode == PACKAGE_STREAM_DETECT )
|
|
m_bRawStream = ParsePackageRawStream();
|
|
else if ( m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
m_bRawStream = true;
|
|
|
|
bool bBackgroundThreadDeflate = false;
|
|
bool bTransportOwnEncrStreamAsRaw = false;
|
|
// During the storing the original size of the stream can be changed
|
|
// TODO/LATER: get rid of this hack
|
|
if (!m_bRawStream)
|
|
{
|
|
m_nOwnStreamOrigSize = aEntry.nSize;
|
|
}
|
|
|
|
bool bUseNonSeekableAccess = false;
|
|
uno::Reference < io::XInputStream > xStream;
|
|
if ( !IsPackageMember() && !m_bRawStream && !bToBeEncrypted && bToBeCompressed )
|
|
{
|
|
// the stream is not a package member, not a raw stream,
|
|
// it should not be encrypted and it should be compressed,
|
|
// in this case nonseekable access can be used
|
|
|
|
xStream = m_xStream;
|
|
uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY );
|
|
|
|
bUseNonSeekableAccess = ( xStream.is() && !xSeek.is() );
|
|
}
|
|
|
|
if ( !bUseNonSeekableAccess )
|
|
{
|
|
xStream = getRawData();
|
|
|
|
if ( !xStream.is() )
|
|
{
|
|
OSL_FAIL( "ZipPackageStream didn't have a stream associated with it, skipping!" );
|
|
return false;
|
|
}
|
|
|
|
uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY );
|
|
try
|
|
{
|
|
if ( xSeek.is() )
|
|
{
|
|
// If the stream is a raw one, then we should be positioned
|
|
// at the beginning of the actual data
|
|
if ( !bToBeCompressed || m_bRawStream )
|
|
{
|
|
// The raw stream can neither be encrypted nor connected
|
|
OSL_ENSURE( !m_bRawStream || !(bToBeCompressed || bToBeEncrypted), "The stream is already encrypted!" );
|
|
xSeek->seek ( m_bRawStream ? m_nMagicalHackPos : 0 );
|
|
ImplSetStoredData ( *pTempEntry, xStream );
|
|
}
|
|
else if ( bToBeEncrypted )
|
|
{
|
|
// this is the correct original size
|
|
m_nOwnStreamOrigSize = xSeek->getLength();
|
|
}
|
|
|
|
xSeek->seek ( 0 );
|
|
}
|
|
else
|
|
{
|
|
// Okay, we don't have an xSeekable stream. This is possibly bad.
|
|
// check if it's one of our own streams, if it is then we know that
|
|
// each time we ask for it we'll get a new stream that will be
|
|
// at position zero...otherwise, assert and skip this stream...
|
|
if ( IsPackageMember() )
|
|
{
|
|
// if the password has been changed then the stream should not be package member any more
|
|
if ( m_bIsEncrypted && m_bToBeEncrypted )
|
|
{
|
|
// Should be handled close to the raw stream handling
|
|
bTransportOwnEncrStreamAsRaw = true;
|
|
pTempEntry->nMethod = STORED;
|
|
|
|
// TODO/LATER: get rid of this situation
|
|
// this size should be different from the one that will be stored in manifest.xml
|
|
// it is used in storing algorithms and after storing the correct size will be set
|
|
pTempEntry->nSize = pTempEntry->nCompressedSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
catch ( uno::Exception& )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( bToBeEncrypted || m_bRawStream || bTransportOwnEncrStreamAsRaw )
|
|
{
|
|
if ( bToBeEncrypted && !bTransportOwnEncrStreamAsRaw )
|
|
{
|
|
uno::Sequence<sal_Int8> aSalt(16);
|
|
// note: for GCM it's particularly important that IV is unique
|
|
uno::Sequence<sal_Int8> aVector(GetIVSize());
|
|
if (rtl_random_getBytes(nullptr, aSalt.getArray(), 16) != rtl_Random_E_None)
|
|
{
|
|
throw uno::RuntimeException(u"rtl_random_getBytes failed"_ustr);
|
|
}
|
|
if (rtl_random_getBytes(nullptr, aVector.getArray(), aVector.getLength()) != rtl_Random_E_None)
|
|
{
|
|
throw uno::RuntimeException(u"rtl_random_getBytes failed"_ustr);
|
|
}
|
|
if ( !m_bHaveOwnKey )
|
|
{
|
|
m_aEncryptionKey = rEncryptionKey;
|
|
m_aStorageEncryptionKeys.realloc( 0 );
|
|
}
|
|
|
|
setInitialisationVector ( aVector );
|
|
setSalt ( aSalt );
|
|
setIterationCount(oPBKDF2IterationCount);
|
|
setArgon2Args(oArgon2Args);
|
|
}
|
|
|
|
// last property is digest, which is inserted later if we didn't have
|
|
// a magic header
|
|
aPropSet.realloc(PKG_SIZE_ENCR_MNFST);
|
|
pPropSet = aPropSet.getArray();
|
|
pPropSet[PKG_MNFST_INIVECTOR].Name = "InitialisationVector";
|
|
pPropSet[PKG_MNFST_INIVECTOR].Value <<= m_xBaseEncryptionData->m_aInitVector;
|
|
pPropSet[PKG_MNFST_SALT].Name = "Salt";
|
|
pPropSet[PKG_MNFST_SALT].Value <<= m_xBaseEncryptionData->m_aSalt;
|
|
if (m_xBaseEncryptionData->m_oArgon2Args)
|
|
{
|
|
pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction";
|
|
pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::Argon2id;
|
|
pPropSet[PKG_MNFST_ARGON2ARGS].Name = "Argon2Args";
|
|
uno::Sequence<sal_Int32> const args{
|
|
::std::get<0>(*m_xBaseEncryptionData->m_oArgon2Args),
|
|
::std::get<1>(*m_xBaseEncryptionData->m_oArgon2Args),
|
|
::std::get<2>(*m_xBaseEncryptionData->m_oArgon2Args) };
|
|
pPropSet[PKG_MNFST_ARGON2ARGS].Value <<= args;
|
|
}
|
|
else if (m_xBaseEncryptionData->m_oPBKDFIterationCount)
|
|
{
|
|
pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction";
|
|
pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PBKDF2;
|
|
pPropSet[PKG_MNFST_ITERATION].Name = "IterationCount";
|
|
pPropSet[PKG_MNFST_ITERATION].Value <<= *m_xBaseEncryptionData->m_oPBKDFIterationCount;
|
|
}
|
|
else
|
|
{
|
|
pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction";
|
|
pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P;
|
|
}
|
|
|
|
// Need to store the uncompressed size in the manifest
|
|
OSL_ENSURE( m_nOwnStreamOrigSize >= 0, "The stream size was not correctly initialized!" );
|
|
pPropSet[PKG_MNFST_UCOMPSIZE].Name = "Size";
|
|
pPropSet[PKG_MNFST_UCOMPSIZE].Value <<= m_nOwnStreamOrigSize;
|
|
|
|
if ( m_bRawStream || bTransportOwnEncrStreamAsRaw )
|
|
{
|
|
::rtl::Reference< EncryptionData > xEncData = GetEncryptionData();
|
|
if ( !xEncData.is() )
|
|
throw uno::RuntimeException();
|
|
|
|
pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty;
|
|
pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg;
|
|
pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty;
|
|
pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID;
|
|
if (xEncData->m_oCheckAlg)
|
|
{
|
|
assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C);
|
|
pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty;
|
|
pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest;
|
|
pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty;
|
|
pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg;
|
|
}
|
|
pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty;
|
|
pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bSuccess = true;
|
|
// If the entry is already stored in the zip file in the format we
|
|
// want for this write...copy it raw
|
|
if ( !bUseNonSeekableAccess
|
|
&& ( m_bRawStream || bTransportOwnEncrStreamAsRaw
|
|
|| ( IsPackageMember() && !bToBeEncrypted
|
|
&& ( ( aEntry.nMethod == DEFLATED && bToBeCompressed )
|
|
|| ( aEntry.nMethod == STORED && !bToBeCompressed ) ) ) ) )
|
|
{
|
|
// If it's a PackageMember, then it's an unbuffered stream and we need
|
|
// to get a new version of it as we can't seek backwards.
|
|
if ( IsPackageMember() )
|
|
{
|
|
xStream = getRawData();
|
|
if ( !xStream.is() )
|
|
{
|
|
// Make sure that we actually _got_ a new one !
|
|
return false;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if ( m_bRawStream )
|
|
xStream->skipBytes( m_nMagicalHackPos );
|
|
|
|
ZipOutputStream::setEntry(*pTempEntry);
|
|
rZipOut.writeLOC(std::move(pAutoTempEntry));
|
|
|
|
uno::Sequence < sal_Int8 > aSeq ( n_ConstBufferSize );
|
|
sal_Int32 nLength;
|
|
|
|
do
|
|
{
|
|
nLength = xStream->readBytes( aSeq, n_ConstBufferSize );
|
|
if (nLength != n_ConstBufferSize)
|
|
aSeq.realloc(nLength);
|
|
|
|
rZipOut.rawWrite(aSeq);
|
|
}
|
|
while ( nLength == n_ConstBufferSize );
|
|
|
|
rZipOut.rawCloseEntry();
|
|
}
|
|
catch ( ZipException& )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
catch ( io::IOException& )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This stream is definitely not a raw stream
|
|
|
|
// If nonseekable access is used the stream should be at the beginning and
|
|
// is useless after the storing. Thus if the storing fails the package should
|
|
// be thrown away ( as actually it is done currently )!
|
|
// To allow to reuse the package after the error, the optimization must be removed!
|
|
|
|
// If it's a PackageMember, then our previous reference held a 'raw' stream
|
|
// so we need to re-get it, unencrypted, uncompressed and positioned at the
|
|
// beginning of the stream
|
|
if ( IsPackageMember() )
|
|
{
|
|
xStream = getInputStream();
|
|
if ( !xStream.is() )
|
|
{
|
|
// Make sure that we actually _got_ a new one !
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( bToBeCompressed )
|
|
{
|
|
pTempEntry->nMethod = DEFLATED;
|
|
pTempEntry->nCrc = -1;
|
|
pTempEntry->nCompressedSize = pTempEntry->nSize = -1;
|
|
}
|
|
|
|
uno::Reference< io::XSeekable > xSeek(xStream, uno::UNO_QUERY);
|
|
// It's not worth to deflate jpegs to save ~1% in a slow process
|
|
// Unfortunately, does not work for streams protected by password
|
|
if (xSeek.is() && msMediaType.endsWith("/jpeg") && !m_bToBeEncrypted && !m_bToBeCompressed)
|
|
{
|
|
ImplSetStoredData(*pTempEntry, xStream);
|
|
xSeek->seek(0);
|
|
}
|
|
|
|
try
|
|
{
|
|
ZipOutputStream::setEntry(*pTempEntry);
|
|
// the entry is provided to the ZipOutputStream that will delete it
|
|
|
|
if (pTempEntry->nMethod == STORED)
|
|
{
|
|
sal_Int32 nLength;
|
|
uno::Sequence< sal_Int8 > aSeq(n_ConstBufferSize);
|
|
rZipOut.writeLOC(std::move(pAutoTempEntry), bToBeEncrypted);
|
|
do
|
|
{
|
|
nLength = xStream->readBytes(aSeq, n_ConstBufferSize);
|
|
if (nLength != n_ConstBufferSize)
|
|
aSeq.realloc(nLength);
|
|
|
|
rZipOut.rawWrite(aSeq);
|
|
}
|
|
while ( nLength == n_ConstBufferSize );
|
|
rZipOut.rawCloseEntry(bToBeEncrypted);
|
|
}
|
|
else
|
|
{
|
|
// tdf#89236 Encrypting in a background thread does not work
|
|
bBackgroundThreadDeflate = !bToBeEncrypted;
|
|
// Do not deflate small streams using threads. XSeekable's getLength()
|
|
// gives the full size, XInputStream's available() may not be
|
|
// the full size, but it appears that at this point it usually is.
|
|
sal_Int64 estimatedSize = xSeek.is() ? xSeek->getLength() : xStream->available();
|
|
|
|
if (estimatedSize > 1000000)
|
|
{
|
|
// Use ThreadDeflater which will split the stream into blocks and compress
|
|
// them in threads, but not in background (i.e. writeStream() will block).
|
|
// This is suitable for large data.
|
|
bBackgroundThreadDeflate = false;
|
|
rZipOut.writeLOC(std::move(pAutoTempEntry), bToBeEncrypted);
|
|
ZipOutputEntryParallel aZipEntry(rZipOut.getStream(), m_xContext, pTempEntry, this, bToBeEncrypted);
|
|
aZipEntry.writeStream(xStream);
|
|
rZipOut.rawCloseEntry(bToBeEncrypted);
|
|
}
|
|
else if (bBackgroundThreadDeflate && estimatedSize > 100000)
|
|
{
|
|
// tdf#93553 limit to a useful amount of pending tasks. Having way too many
|
|
// tasks pending may use a lot of memory. Take number of available
|
|
// cores and allow 4-times the amount for having the queue well filled. The
|
|
// 2nd parameter is the time to wait between cleanups in 10th of a second.
|
|
// Both values may be added to the configuration settings if needed.
|
|
static std::size_t nAllowedTasks(comphelper::ThreadPool::getPreferredConcurrency() * 4); //TODO: overflow
|
|
rZipOut.reduceScheduledThreadTasksToGivenNumberOrLess(nAllowedTasks);
|
|
|
|
// Start a new thread task deflating this zip entry
|
|
ZipOutputEntryInThread *pZipEntry = new ZipOutputEntryInThread(
|
|
m_xContext, std::move(pAutoTempEntry), this, bToBeEncrypted);
|
|
rZipOut.addDeflatingThreadTask( pZipEntry,
|
|
pZipEntry->createTask( rZipOut.getThreadTaskTag(), xStream) );
|
|
}
|
|
else
|
|
{
|
|
bBackgroundThreadDeflate = false;
|
|
rZipOut.writeLOC(std::move(pAutoTempEntry), bToBeEncrypted);
|
|
ZipOutputEntry aZipEntry(rZipOut.getStream(), m_xContext, pTempEntry, this, bToBeEncrypted);
|
|
aZipEntry.writeStream(xStream);
|
|
rZipOut.rawCloseEntry(bToBeEncrypted);
|
|
}
|
|
}
|
|
}
|
|
catch ( ZipException& )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
catch ( io::IOException& )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
if ( bToBeEncrypted )
|
|
{
|
|
::rtl::Reference< EncryptionData > xEncData = GetEncryptionData();
|
|
if ( !xEncData.is() )
|
|
throw uno::RuntimeException();
|
|
|
|
// very confusing: half the encryption properties are
|
|
// unconditionally added above and the other half conditionally;
|
|
// assert that we have the expected group and not duplicates
|
|
assert(std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == "Salt"; }));
|
|
assert(!std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == sEncryptionAlgProperty; }));
|
|
|
|
pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty;
|
|
pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg;
|
|
pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty;
|
|
pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID;
|
|
if (xEncData->m_oCheckAlg)
|
|
{
|
|
assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C);
|
|
pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty;
|
|
pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest;
|
|
pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty;
|
|
pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg;
|
|
}
|
|
pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty;
|
|
pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize;
|
|
|
|
SetIsEncrypted ( true );
|
|
}
|
|
}
|
|
|
|
if (bSuccess && !bBackgroundThreadDeflate)
|
|
successfullyWritten(pTempEntry);
|
|
|
|
if ( aPropSet.hasElements()
|
|
&& ( m_nFormat == embed::StorageFormats::PACKAGE || m_nFormat == embed::StorageFormats::OFOPXML ) )
|
|
rManList.push_back( aPropSet );
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
void ZipPackageStream::successfullyWritten( ZipEntry const *pEntry )
|
|
{
|
|
if ( !IsPackageMember() )
|
|
{
|
|
if ( m_xStream.is() )
|
|
{
|
|
m_xStream->closeInput();
|
|
m_xStream.clear();
|
|
m_bHasSeekable = false;
|
|
}
|
|
SetPackageMember ( true );
|
|
}
|
|
|
|
if ( m_bRawStream )
|
|
{
|
|
// the raw stream was integrated and now behaves
|
|
// as usual encrypted stream
|
|
SetToBeEncrypted( true );
|
|
}
|
|
|
|
// Then copy it back afterwards...
|
|
aEntry = *pEntry;
|
|
|
|
aEntry.nOffset *= -1;
|
|
}
|
|
|
|
void ZipPackageStream::SetPackageMember( bool bNewValue )
|
|
{
|
|
if ( bNewValue )
|
|
{
|
|
m_nStreamMode = PACKAGE_STREAM_PACKAGEMEMBER;
|
|
m_nMagicalHackPos = 0;
|
|
}
|
|
else if ( m_nStreamMode == PACKAGE_STREAM_PACKAGEMEMBER )
|
|
m_nStreamMode = PACKAGE_STREAM_NOTSET; // must be reset
|
|
}
|
|
|
|
// XActiveDataSink
|
|
void SAL_CALL ZipPackageStream::setInputStream( const uno::Reference< io::XInputStream >& aStream )
|
|
{
|
|
// if seekable access is required the wrapping will be done on demand
|
|
m_xStream = aStream;
|
|
m_oImportedAlgorithms.reset();
|
|
m_bHasSeekable = false;
|
|
SetPackageMember ( false );
|
|
aEntry.nTime = -1;
|
|
m_nStreamMode = PACKAGE_STREAM_DETECT;
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > ZipPackageStream::getRawData()
|
|
{
|
|
try
|
|
{
|
|
if ( IsPackageMember() )
|
|
{
|
|
::std::optional<sal_Int64> oDecryptedSize;
|
|
if (m_bIsEncrypted)
|
|
{
|
|
oDecryptedSize.emplace(m_nOwnStreamOrigSize);
|
|
}
|
|
return m_rZipPackage.getZipFile().getRawData( aEntry, GetEncryptionData(),
|
|
oDecryptedSize, m_rZipPackage.GetSharedMutexRef(), false/*bUseBufferedStream*/ );
|
|
}
|
|
else if ( GetOwnSeekStream().is() )
|
|
{
|
|
return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() );
|
|
}
|
|
else
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
catch ( ZipException & )//rException )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "package", "" );
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
catch ( Exception & )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!" );
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getInputStream()
|
|
{
|
|
try
|
|
{
|
|
if ( IsPackageMember() )
|
|
{
|
|
::std::optional<sal_Int64> oDecryptedSize;
|
|
if (m_bIsEncrypted)
|
|
{
|
|
oDecryptedSize.emplace(m_nOwnStreamOrigSize);
|
|
}
|
|
return m_rZipPackage.getZipFile().getInputStream(aEntry, GetEncryptionData(),
|
|
oDecryptedSize, m_rZipPackage.GetSharedMutexRef());
|
|
}
|
|
else if ( GetOwnSeekStream().is() )
|
|
{
|
|
return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() );
|
|
}
|
|
else
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
catch ( ZipException & )//rException )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "package", "" );
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
catch ( const Exception & )
|
|
{
|
|
TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!");
|
|
return uno::Reference < io::XInputStream > ();
|
|
}
|
|
}
|
|
|
|
// XDataSinkEncrSupport
|
|
uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getDataStream()
|
|
{
|
|
// There is no stream attached to this object
|
|
if ( m_nStreamMode == PACKAGE_STREAM_NOTSET )
|
|
return uno::Reference< io::XInputStream >();
|
|
|
|
// this method can not be used together with old approach
|
|
if ( m_nStreamMode == PACKAGE_STREAM_DETECT )
|
|
throw packages::zip::ZipIOException(THROW_WHERE );
|
|
|
|
if ( IsPackageMember() )
|
|
{
|
|
uno::Reference< io::XInputStream > xResult;
|
|
::std::optional<sal_Int64> oDecryptedSize;
|
|
if (m_bIsEncrypted)
|
|
{
|
|
oDecryptedSize.emplace(m_nOwnStreamOrigSize);
|
|
}
|
|
try
|
|
{
|
|
xResult = m_rZipPackage.getZipFile().getDataStream(aEntry,
|
|
GetEncryptionData(Bugs::None), oDecryptedSize,
|
|
m_rZipPackage.GetSharedMutexRef());
|
|
}
|
|
catch( const packages::WrongPasswordException& )
|
|
{
|
|
// note: due to SHA1 check this fallback is only done for
|
|
// * ODF 1.2 files written by OOo < 3.4beta / LO < 3.5
|
|
// * ODF 1.1/OOoXML files written by any version
|
|
if (m_oImportedAlgorithms
|
|
&& m_oImportedAlgorithms->nImportedStartKeyAlgorithm == xml::crypto::DigestID::SHA1)
|
|
{
|
|
SAL_WARN("package", "ZipPackageStream::getDataStream(): SHA1 mismatch, trying fallbacks...");
|
|
try
|
|
{ // tdf#114939 try with legacy StarOffice SHA1 bug
|
|
xResult = m_rZipPackage.getZipFile().getDataStream(aEntry,
|
|
GetEncryptionData(Bugs::WrongSHA1), oDecryptedSize,
|
|
m_rZipPackage.GetSharedMutexRef());
|
|
return xResult;
|
|
}
|
|
catch (const packages::WrongPasswordException&)
|
|
{
|
|
/* ignore and try next... */
|
|
}
|
|
|
|
try
|
|
{
|
|
// rhbz#1013844 / fdo#47482 workaround for the encrypted
|
|
// OpenOffice.org 1.0 documents generated by Libreoffice <=
|
|
// 3.6 with the new encryption format and using SHA256, but
|
|
// missing a specified startkey of SHA256
|
|
|
|
// force SHA256 and see if that works
|
|
m_oImportedAlgorithms->nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA256;
|
|
xResult = m_rZipPackage.getZipFile().getDataStream(aEntry,
|
|
GetEncryptionData(), oDecryptedSize,
|
|
m_rZipPackage.GetSharedMutexRef());
|
|
return xResult;
|
|
}
|
|
catch (const packages::WrongPasswordException&)
|
|
{
|
|
// if that didn't work, restore to SHA1 and trundle through the *other* earlier
|
|
// bug fix
|
|
m_oImportedAlgorithms->nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA1;
|
|
}
|
|
|
|
// workaround for the encrypted documents generated with the old OOo1.x bug.
|
|
if ( !m_bUseWinEncoding )
|
|
{
|
|
xResult = m_rZipPackage.getZipFile().getDataStream(aEntry,
|
|
GetEncryptionData(Bugs::WinEncodingWrongSHA1),
|
|
oDecryptedSize, m_rZipPackage.GetSharedMutexRef());
|
|
m_bUseWinEncoding = true;
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
return xResult;
|
|
}
|
|
else if ( m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
return ZipFile::StaticGetDataFromRawStream( m_rZipPackage.GetSharedMutexRef(), m_xContext, GetOwnSeekStream(), GetEncryptionData() );
|
|
else if ( GetOwnSeekStream().is() )
|
|
{
|
|
return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() );
|
|
}
|
|
else
|
|
return uno::Reference< io::XInputStream >();
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getRawStream()
|
|
{
|
|
// There is no stream attached to this object
|
|
if ( m_nStreamMode == PACKAGE_STREAM_NOTSET )
|
|
return uno::Reference< io::XInputStream >();
|
|
|
|
// this method can not be used together with old approach
|
|
if ( m_nStreamMode == PACKAGE_STREAM_DETECT )
|
|
throw packages::zip::ZipIOException(THROW_WHERE );
|
|
|
|
if ( IsPackageMember() )
|
|
{
|
|
if ( !m_bIsEncrypted || !GetEncryptionData().is() )
|
|
throw packages::NoEncryptionException(THROW_WHERE );
|
|
|
|
return m_rZipPackage.getZipFile().getWrappedRawStream(aEntry, GetEncryptionData(),
|
|
m_nOwnStreamOrigSize, msMediaType, m_rZipPackage.GetSharedMutexRef());
|
|
}
|
|
else if ( GetOwnSeekStream().is() )
|
|
{
|
|
if ( m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
{
|
|
return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() );
|
|
}
|
|
else if ( m_nStreamMode == PACKAGE_STREAM_DATA && m_bToBeEncrypted )
|
|
return TryToGetRawFromDataStream( true );
|
|
}
|
|
|
|
throw packages::NoEncryptionException(THROW_WHERE );
|
|
}
|
|
|
|
void SAL_CALL ZipPackageStream::setDataStream( const uno::Reference< io::XInputStream >& aStream )
|
|
{
|
|
setInputStream( aStream );
|
|
m_nStreamMode = PACKAGE_STREAM_DATA;
|
|
}
|
|
|
|
void SAL_CALL ZipPackageStream::setRawStream( const uno::Reference< io::XInputStream >& aStream )
|
|
{
|
|
// wrap the stream in case it is not seekable
|
|
uno::Reference< io::XInputStream > xNewStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( aStream, m_xContext );
|
|
uno::Reference< io::XSeekable > xSeek( xNewStream, UNO_QUERY_THROW );
|
|
xSeek->seek( 0 );
|
|
uno::Reference< io::XInputStream > xOldStream = m_xStream;
|
|
m_xStream = std::move(xNewStream);
|
|
if ( !ParsePackageRawStream() )
|
|
{
|
|
m_xStream = std::move(xOldStream);
|
|
throw packages::NoRawFormatException(THROW_WHERE );
|
|
}
|
|
|
|
// the raw stream MUST have seekable access
|
|
m_bHasSeekable = true;
|
|
|
|
SetPackageMember ( false );
|
|
aEntry.nTime = -1;
|
|
m_nStreamMode = PACKAGE_STREAM_RAW;
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getPlainRawStream()
|
|
{
|
|
// There is no stream attached to this object
|
|
if ( m_nStreamMode == PACKAGE_STREAM_NOTSET )
|
|
return uno::Reference< io::XInputStream >();
|
|
|
|
// this method can not be used together with old approach
|
|
if ( m_nStreamMode == PACKAGE_STREAM_DETECT )
|
|
throw packages::zip::ZipIOException(THROW_WHERE );
|
|
|
|
if ( IsPackageMember() )
|
|
{
|
|
::std::optional<sal_Int64> oDecryptedSize;
|
|
if (m_bIsEncrypted)
|
|
{
|
|
oDecryptedSize.emplace(m_nOwnStreamOrigSize);
|
|
}
|
|
return m_rZipPackage.getZipFile().getRawData(aEntry, GetEncryptionData(),
|
|
oDecryptedSize, m_rZipPackage.GetSharedMutexRef());
|
|
}
|
|
else if ( GetOwnSeekStream().is() )
|
|
{
|
|
if ( m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
{
|
|
// the header should not be returned here
|
|
return GetRawEncrStreamNoHeaderCopy();
|
|
}
|
|
else if ( m_nStreamMode == PACKAGE_STREAM_DATA )
|
|
return TryToGetRawFromDataStream( false );
|
|
}
|
|
|
|
return uno::Reference< io::XInputStream >();
|
|
}
|
|
|
|
// XPropertySet
|
|
void SAL_CALL ZipPackageStream::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
|
|
{
|
|
if ( aPropertyName == "MediaType" )
|
|
{
|
|
if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE && m_rZipPackage.getFormat() != embed::StorageFormats::OFOPXML )
|
|
throw beans::PropertyVetoException(THROW_WHERE );
|
|
|
|
if ( !(aValue >>= msMediaType) )
|
|
throw IllegalArgumentException(THROW_WHERE "MediaType must be a string!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
if ( !msMediaType.isEmpty() )
|
|
{
|
|
if ( msMediaType.indexOf ( "text" ) != -1
|
|
|| msMediaType == "application/vnd.sun.star.oleobject" )
|
|
m_bToBeCompressed = true;
|
|
else if ( !m_bCompressedIsSetFromOutside )
|
|
m_bToBeCompressed = false;
|
|
}
|
|
}
|
|
else if ( aPropertyName == "Size" )
|
|
{
|
|
if (!(aValue >>= m_nOwnStreamOrigSize))
|
|
throw IllegalArgumentException(THROW_WHERE "Wrong type for Size property!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
}
|
|
else if ( aPropertyName == "Encrypted" )
|
|
{
|
|
if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE )
|
|
throw beans::PropertyVetoException(THROW_WHERE );
|
|
|
|
bool bEnc = false;
|
|
if ( !(aValue >>= bEnc) )
|
|
throw IllegalArgumentException(THROW_WHERE "Wrong type for Encrypted property!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
// In case of new raw stream, the stream must not be encrypted on storing
|
|
if ( bEnc && m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
m_bToBeEncrypted = bEnc;
|
|
if ( m_bToBeEncrypted && !m_xBaseEncryptionData.is() )
|
|
m_xBaseEncryptionData = new BaseEncryptionData;
|
|
|
|
}
|
|
else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY )
|
|
{
|
|
if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE )
|
|
throw beans::PropertyVetoException(THROW_WHERE );
|
|
|
|
uno::Sequence< sal_Int8 > aNewKey;
|
|
|
|
if ( !( aValue >>= aNewKey ) )
|
|
{
|
|
OUString sTempString;
|
|
if ( !(aValue >>= sTempString) )
|
|
throw IllegalArgumentException(THROW_WHERE "Wrong type for EncryptionKey property!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
sal_Int32 nPathLength = sTempString.getLength();
|
|
Sequence < sal_Int8 > aSequence ( nPathLength );
|
|
sal_Int8 *pArray = aSequence.getArray();
|
|
const sal_Unicode *pChar = sTempString.getStr();
|
|
for ( sal_Int32 i = 0; i < nPathLength; i++ )
|
|
pArray[i] = static_cast < sal_Int8 > ( pChar[i] );
|
|
aNewKey = std::move(aSequence);
|
|
}
|
|
|
|
if ( aNewKey.hasElements() )
|
|
{
|
|
if ( !m_xBaseEncryptionData.is() )
|
|
m_xBaseEncryptionData = new BaseEncryptionData;
|
|
|
|
m_aEncryptionKey = std::move(aNewKey);
|
|
// In case of new raw stream, the stream must not be encrypted on storing
|
|
m_bHaveOwnKey = true;
|
|
if ( m_nStreamMode != PACKAGE_STREAM_RAW )
|
|
m_bToBeEncrypted = true;
|
|
}
|
|
else
|
|
{
|
|
m_bHaveOwnKey = false;
|
|
m_aEncryptionKey.realloc( 0 );
|
|
}
|
|
|
|
m_aStorageEncryptionKeys.realloc( 0 );
|
|
}
|
|
else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
|
|
{
|
|
if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE )
|
|
throw beans::PropertyVetoException(THROW_WHERE );
|
|
|
|
uno::Sequence< beans::NamedValue > aKeys;
|
|
if ( !( aValue >>= aKeys ) )
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "Wrong type for StorageEncryptionKeys property!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
}
|
|
|
|
if ( aKeys.hasElements() )
|
|
{
|
|
if ( !m_xBaseEncryptionData.is() )
|
|
m_xBaseEncryptionData = new BaseEncryptionData;
|
|
|
|
m_aStorageEncryptionKeys = std::move(aKeys);
|
|
|
|
// In case of new raw stream, the stream must not be encrypted on storing
|
|
m_bHaveOwnKey = true;
|
|
if ( m_nStreamMode != PACKAGE_STREAM_RAW )
|
|
m_bToBeEncrypted = true;
|
|
}
|
|
else
|
|
{
|
|
m_bHaveOwnKey = false;
|
|
m_aStorageEncryptionKeys.realloc( 0 );
|
|
}
|
|
|
|
m_aEncryptionKey.realloc( 0 );
|
|
}
|
|
else if ( aPropertyName == "Compressed" )
|
|
{
|
|
bool bCompr = false;
|
|
|
|
if ( !(aValue >>= bCompr) )
|
|
throw IllegalArgumentException(THROW_WHERE "Wrong type for Compressed property!",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
// In case of new raw stream, the stream must not be encrypted on storing
|
|
if ( bCompr && m_nStreamMode == PACKAGE_STREAM_RAW )
|
|
throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing",
|
|
uno::Reference< XInterface >(),
|
|
2 );
|
|
|
|
m_bToBeCompressed = bCompr;
|
|
m_bCompressedIsSetFromOutside = true;
|
|
}
|
|
else
|
|
throw beans::UnknownPropertyException(aPropertyName);
|
|
}
|
|
|
|
Any SAL_CALL ZipPackageStream::getPropertyValue( const OUString& PropertyName )
|
|
{
|
|
if ( PropertyName == "MediaType" )
|
|
{
|
|
return Any(msMediaType);
|
|
}
|
|
else if ( PropertyName == "Size" )
|
|
{
|
|
return Any(m_nOwnStreamOrigSize);
|
|
}
|
|
else if ( PropertyName == "Encrypted" )
|
|
{
|
|
return Any((m_nStreamMode == PACKAGE_STREAM_RAW) || m_bToBeEncrypted);
|
|
}
|
|
else if ( PropertyName == "WasEncrypted" )
|
|
{
|
|
return Any(m_bIsEncrypted);
|
|
}
|
|
else if ( PropertyName == "Compressed" )
|
|
{
|
|
return Any(m_bToBeCompressed);
|
|
}
|
|
else if ( PropertyName == ENCRYPTION_KEY_PROPERTY )
|
|
{
|
|
return Any(m_aEncryptionKey);
|
|
}
|
|
else if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
|
|
{
|
|
return Any(m_aStorageEncryptionKeys);
|
|
}
|
|
else
|
|
throw beans::UnknownPropertyException(PropertyName);
|
|
}
|
|
|
|
void ZipPackageStream::setSize ( const sal_Int64 nNewSize )
|
|
{
|
|
m_nOwnStreamOrigSize = nNewSize;
|
|
}
|
|
|
|
OUString ZipPackageStream::getImplementationName()
|
|
{
|
|
return u"ZipPackageStream"_ustr;
|
|
}
|
|
|
|
Sequence< OUString > ZipPackageStream::getSupportedServiceNames()
|
|
{
|
|
return { u"com.sun.star.packages.PackageStream"_ustr };
|
|
}
|
|
|
|
sal_Bool SAL_CALL ZipPackageStream::supportsService( OUString const & rServiceName )
|
|
{
|
|
return cppu::supportsService(this, rServiceName);
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|