/* -*- 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 <com/sun/star/io/BufferSizeExceededException.hpp>
#include <com/sun/star/io/NotConnectedException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/packages/NoEncryptionException.hpp>
#include <com/sun/star/packages/WrongPasswordException.hpp>
#include <com/sun/star/packages/zip/ZipConstants.hpp>
#include <com/sun/star/packages/zip/ZipException.hpp>
#include <com/sun/star/packages/zip/ZipIOException.hpp>
#include <com/sun/star/xml/crypto/XCipherContext.hpp>
#include <com/sun/star/xml/crypto/XDigestContext.hpp>
#include <com/sun/star/xml/crypto/CipherID.hpp>
#include <com/sun/star/xml/crypto/DigestID.hpp>
#include <com/sun/star/xml/crypto/NSSInitializer.hpp>
#include <comphelper/bytereader.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/threadpool.hxx>
#include <rtl/digest.h>
#include <rtl/crc.h>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <algorithm>
#include <iterator>
#include <utility>
#include <vector>
#include <argon2.h>
#include "blowfishcontext.hxx"
#include "sha1context.hxx"
#include <ZipFile.hxx>
#include <ZipEnumeration.hxx>
#include "XUnbufferedStream.hxx"
#include "XBufferedThreadedStream.hxx"
#include <PackageConstants.hxx>
#include <EncryptedDataHeader.hxx>
#include <EncryptionData.hxx>
#include "MemoryByteGrabber.hxx"
#include <CRC32.hxx>
#include <package/InflateZlib.hxx>
#include <InflaterBytesZlib.hxx>
using namespace com::sun::star;
using namespace com::sun::star::io;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace com::sun::star::packages;
using namespace com::sun::star::packages::zip;
using namespace com::sun::star::packages::zip::ZipConstants;
using ZipUtils::Inflater;
#if OSL_DEBUG_LEVEL >
0
#define THROW_WHERE SAL_WHERE
#else
#define THROW_WHERE
""
#endif
/** This class is used to read entries from a zip file
*/
ZipFile::ZipFile( rtl::Reference< comphelper::RefCountedMutex > aMutexHolder,
uno::Reference < XInputStream >
const &xInput,
uno::Reference < XComponentContext > xContext,
bool bInitialise,
bool bForceRecovery,
Checks
const checks)
: m_aMutexHolder(std::move( aMutexHolder ))
, m_Checks(checks)
, aGrabber( xInput )
, xStream(xInput)
, m_xContext (std::move( xContext ))
, bRecoveryMode( bForceRecovery )
{
if (bInitialise)
{
if ( bForceRecovery )
{
recover();
}
else if ( readCEN() == -
1 )
{
aEntries.clear();
m_EntriesInsensitive.clear();
throw ZipException(u
"stream data looks to be broken" _ustr );
}
}
}
ZipFile::~ZipFile()
{
aEntries.clear();
}
void ZipFile::setInputStream (
const uno::Reference < XInputStream >& xNewStream )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
xStream = xNewStream;
aGrabber.setInputStream ( xStream );
}
uno::Reference< xml::crypto::XDigestContext > ZipFile::StaticGetDigestContextForCheck
sum( const uno::Reference< uno::XComponentContext >& xArgContext, const ::rtl::Reference< EncryptionData >& xEncryptionData )
{
assert(xEncryptionData->m_oCheckAlg); // callers checked it already
uno::Reference< xml::crypto::XDigestContext > xDigestContext;
if (*xEncryptionData->m_oCheckAlg == xml::crypto::DigestID::SHA256_1K)
{
uno::Reference< uno::XComponentContext > xContext = xArgContext;
if ( !xContext.is() )
xContext = comphelper::getProcessComponentContext();
uno::Reference< xml::crypto::XNSSInitializer > xDigestContextSupplier = xml::crypto::NSSInitializer::create( xContext );
xDigestContext.set(xDigestContextSupplier->getDigestContext(
*xEncryptionData->m_oCheckAlg, uno::Sequence<beans::NamedValue>()),
uno::UNO_SET_THROW);
}
else if (*xEncryptionData->m_oCheckAlg == xml::crypto::DigestID::SHA1_1K)
{
if (xEncryptionData->m_bTryWrongSHA1)
{
xDigestContext.set(StarOfficeSHA1DigestContext::Create(), uno::UNO_SET_THROW);
}
else
{
xDigestContext.set(CorrectSHA1DigestContext::Create(), uno::UNO_SET_THROW);
}
}
return xDigestContext;
}
uno::Reference< xml::crypto::XCipherContext > ZipFile::StaticGetCipher( const uno::Reference< uno::XComponentContext >& xArgContext, const ::rtl::Reference< EncryptionData >& xEncryptionData, bool bEncrypt )
{
uno::Reference< xml::crypto::XCipherContext > xResult;
if (xEncryptionData->m_nDerivedKeySize < 0 )
{
throw ZipIOException(u"Invalid derived key length!" _ustr );
}
uno::Sequence< sal_Int8 > aDerivedKey( xEncryptionData->m_nDerivedKeySize );
if (!xEncryptionData->m_oPBKDFIterationCount && !xEncryptionData->m_oArgon2Args
&& xEncryptionData->m_nDerivedKeySize == xEncryptionData->m_aKey.getLength())
{
// gpg4libre: no need to derive key, m_aKey is already
// usable as symmetric session key
aDerivedKey = xEncryptionData->m_aKey;
}
else if (xEncryptionData->m_oArgon2Args)
{
// apparently multiple lanes cannot be processed in parallel (the
// implementation will clamp), but it doesn't make sense to have more
// threads than CPUs
uint32_t const threads(::comphelper::ThreadPool::getPreferredConcurrency());
// need to use context to set a fixed version
argon2_context context = {
.out = reinterpret_cast <uint8_t *>(aDerivedKey.getArray()),
.outlen = ::sal::static_int_cast<uint32_t>(aDerivedKey.getLength()),
.pwd = reinterpret_cast <uint8_t *>(xEncryptionData->m_aKey.getArray()),
.pwdlen = ::sal::static_int_cast<uint32_t>(xEncryptionData->m_aKey.getLength()),
.salt = reinterpret_cast <uint8_t *>(xEncryptionData->m_aSalt.getArray()),
.saltlen = ::sal::static_int_cast<uint32_t>(xEncryptionData->m_aSalt.getLength()),
.secret = nullptr, .secretlen = 0 ,
.ad = nullptr, .adlen = 0 ,
.t_cost = ::sal::static_int_cast<uint32_t>(::std::get<0 >(*xEncryptionData->m_oArgon2Args)),
.m_cost = ::sal::static_int_cast<uint32_t>(::std::get<1 >(*xEncryptionData->m_oArgon2Args)),
.lanes = ::sal::static_int_cast<uint32_t>(::std::get<2 >(*xEncryptionData->m_oArgon2Args)),
.threads = threads,
.version = ARGON2_VERSION_13,
.allocate_cbk = nullptr, .free_cbk = nullptr,
.flags = ARGON2_DEFAULT_FLAGS
};
// libargon2 validates all the arguments so don't need to do it here
int const rc = argon2id_ctx(&context);
if (rc != ARGON2_OK)
{
SAL_WARN("package" , "argon2id_ctx failed to derive key: " << argon2_error_message(rc));
throw ZipIOException(u"argon2id_ctx failed to derive key" _ustr);
}
}
else if ( rtl_Digest_E_None != rtl_digest_PBKDF2( reinterpret_cast < sal_uInt8* >( aDerivedKey.getArray() ),
aDerivedKey.getLength(),
reinterpret_cast < const sal_uInt8 * > (xEncryptionData->m_aKey.getConstArray() ),
xEncryptionData->m_aKey.getLength(),
reinterpret_cast < const sal_uInt8 * > ( xEncryptionData->m_aSalt.getConstArray() ),
xEncryptionData->m_aSalt.getLength(),
*xEncryptionData->m_oPBKDFIterationCount) )
{
throw ZipIOException(u"Can not create derived key!" _ustr );
}
if (xEncryptionData->m_nEncAlg == xml::crypto::CipherID::AES_CBC_W3C_PADDING
|| xEncryptionData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C)
{
uno::Reference< uno::XComponentContext > xContext = xArgContext;
if ( !xContext.is() )
xContext = comphelper::getProcessComponentContext();
uno::Reference< xml::crypto::XNSSInitializer > xCipherContextSupplier = xml::crypto::NSSInitializer::create( xContext );
xResult = xCipherContextSupplier->getCipherContext( xEncryptionData->m_nEncAlg, aDerivedKey, xEncryptionData->m_aInitVector, bEncrypt, uno::Sequence< beans::NamedValue >() );
}
else if ( xEncryptionData->m_nEncAlg == xml::crypto::CipherID::BLOWFISH_CFB_8 )
{
xResult = BlowfishCFB8CipherContext::Create( aDerivedKey, xEncryptionData->m_aInitVector, bEncrypt );
}
else
{
throw ZipIOException(u"Unknown cipher algorithm is requested!" _ustr );
}
return xResult;
}
void ZipFile::StaticFillHeader( const ::rtl::Reference< EncryptionData >& rData,
sal_Int64 nSize,
const OUString& aMediaType,
sal_Int8 * & pHeader )
{
// I think it's safe to restrict vector and salt length to 2 bytes !
sal_Int16 nIVLength = static_cast < sal_Int16 > ( rData->m_aInitVector.getLength() );
sal_Int16 nSaltLength = static_cast < sal_Int16 > ( rData->m_aSalt.getLength() );
sal_Int16 nDigestLength = static_cast < sal_Int16 > ( rData->m_aDigest.getLength() );
sal_Int16 nMediaTypeLength = static_cast < sal_Int16 > ( aMediaType.getLength() * sizeof ( sal_Unicode ) );
// First the header
*(pHeader++) = ( n_ConstHeader >> 0 ) & 0 xFF;
*(pHeader++) = ( n_ConstHeader >> 8 ) & 0 xFF;
*(pHeader++) = ( n_ConstHeader >> 16 ) & 0 xFF;
*(pHeader++) = ( n_ConstHeader >> 24 ) & 0 xFF;
// Then the version
*(pHeader++) = ( n_ConstCurrentVersion >> 0 ) & 0 xFF;
*(pHeader++) = ( n_ConstCurrentVersion >> 8 ) & 0 xFF;
// Then the iteration Count
sal_Int32 const nIterationCount = rData->m_oPBKDFIterationCount ? *rData->m_oPBKDFIterationCount : 0 ;
*(pHeader++) = static_cast < sal_Int8 >(( nIterationCount >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nIterationCount >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nIterationCount >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nIterationCount >> 24 ) & 0 xFF);
sal_Int32 const nArgon2t = rData->m_oArgon2Args ? ::std::get<0 >(*rData->m_oArgon2Args) : 0 ;
*(pHeader++) = static_cast <sal_Int8>((nArgon2t >> 0 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2t >> 8 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2t >> 16 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2t >> 24 ) & 0 xFF);
sal_Int32 const nArgon2m = rData->m_oArgon2Args ? ::std::get<1 >(*rData->m_oArgon2Args) : 0 ;
*(pHeader++) = static_cast <sal_Int8>((nArgon2m >> 0 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2m >> 8 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2m >> 16 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2m >> 24 ) & 0 xFF);
sal_Int32 const nArgon2p = rData->m_oArgon2Args ? ::std::get<2 >(*rData->m_oArgon2Args) : 0 ;
*(pHeader++) = static_cast <sal_Int8>((nArgon2p >> 0 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2p >> 8 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2p >> 16 ) & 0 xFF);
*(pHeader++) = static_cast <sal_Int8>((nArgon2p >> 24 ) & 0 xFF);
// FIXME64: need to handle larger sizes
// Then the size:
*(pHeader++) = static_cast < sal_Int8 >(( nSize >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nSize >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nSize >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nSize >> 24 ) & 0 xFF);
// Then the encryption algorithm
sal_Int32 nEncAlgID = rData->m_nEncAlg;
*(pHeader++) = static_cast < sal_Int8 >(( nEncAlgID >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nEncAlgID >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nEncAlgID >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nEncAlgID >> 24 ) & 0 xFF);
// Then the checksum algorithm
sal_Int32 nChecksumAlgID = rData->m_oCheckAlg ? *rData->m_oCheckAlg : 0 ;
*(pHeader++) = static_cast < sal_Int8 >(( nChecksumAlgID >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nChecksumAlgID >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nChecksumAlgID >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nChecksumAlgID >> 24 ) & 0 xFF);
// Then the derived key size
sal_Int32 nDerivedKeySize = rData->m_nDerivedKeySize;
*(pHeader++) = static_cast < sal_Int8 >(( nDerivedKeySize >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nDerivedKeySize >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nDerivedKeySize >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nDerivedKeySize >> 24 ) & 0 xFF);
// Then the start key generation algorithm
sal_Int32 nKeyAlgID = rData->m_nStartKeyGenID;
*(pHeader++) = static_cast < sal_Int8 >(( nKeyAlgID >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nKeyAlgID >> 8 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nKeyAlgID >> 16 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nKeyAlgID >> 24 ) & 0 xFF);
// Then the salt length
*(pHeader++) = static_cast < sal_Int8 >(( nSaltLength >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nSaltLength >> 8 ) & 0 xFF);
// Then the IV length
*(pHeader++) = static_cast < sal_Int8 >(( nIVLength >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nIVLength >> 8 ) & 0 xFF);
// Then the digest length
*(pHeader++) = static_cast < sal_Int8 >(( nDigestLength >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nDigestLength >> 8 ) & 0 xFF);
// Then the mediatype length
*(pHeader++) = static_cast < sal_Int8 >(( nMediaTypeLength >> 0 ) & 0 xFF);
*(pHeader++) = static_cast < sal_Int8 >(( nMediaTypeLength >> 8 ) & 0 xFF);
// Then the salt content
memcpy ( pHeader, rData->m_aSalt.getConstArray(), nSaltLength );
pHeader += nSaltLength;
// Then the IV content
memcpy ( pHeader, rData->m_aInitVector.getConstArray(), nIVLength );
pHeader += nIVLength;
// Then the digest content
memcpy ( pHeader, rData->m_aDigest.getConstArray(), nDigestLength );
pHeader += nDigestLength;
// Then the mediatype itself
memcpy ( pHeader, aMediaType.getStr(), nMediaTypeLength );
pHeader += nMediaTypeLength;
}
bool ZipFile::StaticFillData ( ::rtl::Reference< BaseEncryptionData > const & rData,
sal_Int32 &rEncAlg,
sal_Int32 &rChecksumAlg,
sal_Int32 &rDerivedKeySize,
sal_Int32 &rStartKeyGenID,
sal_Int32 &rSize,
OUString& aMediaType,
const uno::Reference< XInputStream >& rStream )
{
bool bOk = false ;
const sal_Int32 nHeaderSize = n_ConstHeaderSize - 4 ;
Sequence < sal_Int8 > aBuffer ( nHeaderSize );
if ( nHeaderSize == rStream->readBytes ( aBuffer, nHeaderSize ) )
{
sal_Int16 nPos = 0 ;
sal_Int8 *pBuffer = aBuffer.getArray();
sal_Int16 nVersion = pBuffer[nPos++] & 0 xFF;
nVersion |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
if ( nVersion == n_ConstCurrentVersion )
{
sal_Int32 nCount = pBuffer[nPos++] & 0 xFF;
nCount |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
nCount |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
nCount |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
if (nCount != 0 )
{
rData->m_oPBKDFIterationCount.emplace(nCount);
}
else
{
rData->m_oPBKDFIterationCount.reset();
}
sal_Int32 nArgon2t = pBuffer[nPos++] & 0 xFF;
nArgon2t |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
nArgon2t |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
nArgon2t |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
sal_Int32 nArgon2m = pBuffer[nPos++] & 0 xFF;
nArgon2m |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
nArgon2m |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
nArgon2m |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
sal_Int32 nArgon2p = pBuffer[nPos++] & 0 xFF;
nArgon2p |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
nArgon2p |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
nArgon2p |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
if (nArgon2t != 0 && nArgon2m != 0 && nArgon2p != 0 )
{
rData->m_oArgon2Args.emplace(nArgon2t, nArgon2m, nArgon2p);
}
else
{
rData->m_oArgon2Args.reset();
}
rSize = pBuffer[nPos++] & 0 xFF;
rSize |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
rSize |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
rSize |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
rEncAlg = pBuffer[nPos++] & 0 xFF;
rEncAlg |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
rEncAlg |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
rEncAlg |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
rChecksumAlg = pBuffer[nPos++] & 0 xFF;
rChecksumAlg |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
rChecksumAlg |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
rChecksumAlg |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
rDerivedKeySize = pBuffer[nPos++] & 0 xFF;
rDerivedKeySize |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
rDerivedKeySize |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
rDerivedKeySize |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
rStartKeyGenID = pBuffer[nPos++] & 0 xFF;
rStartKeyGenID |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
rStartKeyGenID |= ( pBuffer[nPos++] & 0 xFF ) << 16 ;
rStartKeyGenID |= ( pBuffer[nPos++] & 0 xFF ) << 24 ;
sal_Int16 nSaltLength = pBuffer[nPos++] & 0 xFF;
nSaltLength |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
sal_Int16 nIVLength = ( pBuffer[nPos++] & 0 xFF );
nIVLength |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
sal_Int16 nDigestLength = pBuffer[nPos++] & 0 xFF;
nDigestLength |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
sal_Int16 nMediaTypeLength = pBuffer[nPos++] & 0 xFF;
nMediaTypeLength |= ( pBuffer[nPos++] & 0 xFF ) << 8 ;
if ( nSaltLength == rStream->readBytes ( aBuffer, nSaltLength ) )
{
rData->m_aSalt.realloc ( nSaltLength );
memcpy ( rData->m_aSalt.getArray(), aBuffer.getConstArray(), nSaltLength );
if ( nIVLength == rStream->readBytes ( aBuffer, nIVLength ) )
{
rData->m_aInitVector.realloc ( nIVLength );
memcpy ( rData->m_aInitVector.getArray(), aBuffer.getConstArray(), nIVLength );
if ( nDigestLength == rStream->readBytes ( aBuffer, nDigestLength ) )
{
rData->m_aDigest.realloc ( nDigestLength );
memcpy ( rData->m_aDigest.getArray(), aBuffer.getConstArray(), nDigestLength );
if ( nMediaTypeLength == rStream->readBytes ( aBuffer, nMediaTypeLength ) )
{
aMediaType = OUString( reinterpret_cast <sal_Unicode const *>(aBuffer.getConstArray()),
nMediaTypeLength / sizeof ( sal_Unicode ) );
bOk = true ;
}
}
}
}
}
}
return bOk;
}
#if 0
// for debugging purposes
void CheckSequence( const uno::Sequence< sal_Int8 >& aSequence )
{
if ( aSequence.getLength() )
{
sal_Int32* pPointer = *( (sal_Int32**)&aSequence );
sal_Int32 nSize = *( pPointer + 1 );
sal_Int32 nMemSize = *( pPointer - 2 );
sal_Int32 nUsedMemSize = ( nSize + 4 * sizeof ( sal_Int32 ) );
OSL_ENSURE( nSize == aSequence.getLength() && nUsedMemSize + 7 - ( nUsedMemSize - 1 ) % 8 == nMemSize, "Broken Sequence!" );
}
}
#endif
bool ZipFile::StaticHasValidPassword( const uno::Reference< uno::XComponentContext >& rxContext, const Sequence< sal_Int8 > &aReadBuffer, const ::rtl::Reference< EncryptionData > &rData )
{
assert(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); // should not be called for AEAD
if ( !rData.is() || !rData->m_aKey.hasElements() )
return false ;
bool bRet = false ;
uno::Reference< xml::crypto::XCipherContext > xCipher( StaticGetCipher( rxContext, rData, false ), uno::UNO_SET_THROW );
uno::Sequence< sal_Int8 > aDecryptBuffer;
uno::Sequence< sal_Int8 > aDecryptBuffer2;
try
{
aDecryptBuffer = xCipher->convertWithCipherContext( aReadBuffer );
aDecryptBuffer2 = xCipher->finalizeCipherContextAndDispose();
}
catch ( uno::Exception& )
{
// decryption with padding will throw the exception in finalizing if the buffer represent only part of the stream
// it is no problem, actually this is why we read 32 additional bytes ( two of maximal possible encryption blocks )
}
if ( aDecryptBuffer2.hasElements() )
{
sal_Int32 nOldLen = aDecryptBuffer.getLength();
aDecryptBuffer.realloc( nOldLen + aDecryptBuffer2.getLength() );
memcpy( aDecryptBuffer.getArray() + nOldLen, aDecryptBuffer2.getConstArray(), aDecryptBuffer2.getLength() );
}
if ( aDecryptBuffer.getLength() > n_ConstDigestLength )
aDecryptBuffer.realloc( n_ConstDigestLength );
uno::Sequence< sal_Int8 > aDigestSeq;
uno::Reference< xml::crypto::XDigestContext > xDigestContext( StaticGetDigestContextForChecksum( rxContext, rData ), uno::UNO_SET_THROW );
xDigestContext->updateDigest( aDecryptBuffer );
aDigestSeq = xDigestContext->finalizeDigestAndDispose();
// If we don't have a digest, then we have to assume that the password is correct
if ( rData->m_aDigest.hasElements() &&
( aDigestSeq.getLength() != rData->m_aDigest.getLength() ||
0 != memcmp ( aDigestSeq.getConstArray(),
rData->m_aDigest.getConstArray(),
aDigestSeq.getLength() ) ) )
{
// We should probably tell the user that the password they entered was wrong
}
else
bRet = true ;
return bRet;
}
uno::Reference<io::XInputStream> ZipFile::checkValidPassword(
ZipEntry const & rEntry, ::rtl::Reference<EncryptionData> const & rData,
sal_Int64 const nDecryptedSize,
rtl::Reference<comphelper::RefCountedMutex> const & rMutex)
{
if (rData.is() && rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C)
{
try // the only way to find out: decrypt the whole stream, which will
{ // check the tag
uno::Reference<io::XInputStream> const xRet =
createStreamForZipEntry(rMutex, rEntry, rData, UNBUFF_STREAM_DATA, nDecryptedSize);
// currently XBufferedStream reads the whole stream in its ctor (to
// verify the tag) - in case this gets changed, explicitly seek here
uno::Reference<io::XSeekable> const xSeek(xRet, uno::UNO_QUERY_THROW);
xSeek->seek(xSeek->getLength());
xSeek->seek(0 );
return xRet;
}
catch (uno::Exception const &)
{
return {};
}
}
else if (rData.is() && rData->m_aKey.hasElements())
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
css::uno::Reference < css::io::XSeekable > xSeek(xStream, UNO_QUERY_THROW);
xSeek->seek( rEntry.nOffset );
sal_Int64 nSize = rEntry.nMethod == DEFLATED ? rEntry.nCompressedSize : rEntry.nSize;
// Only want to read enough to verify the digest
if ( nSize > n_ConstDigestDecrypt )
nSize = n_ConstDigestDecrypt;
assert(nSize <= n_ConstDigestDecrypt && nSize >= 0 && "silence bogus coverity overflow_sink" );
Sequence<sal_Int8> aReadBuffer(nSize);
xStream->readBytes( aReadBuffer, nSize );
if (StaticHasValidPassword(m_xContext, aReadBuffer, rData))
{
return createStreamForZipEntry(
rMutex, rEntry, rData, UNBUFF_STREAM_DATA, nDecryptedSize);
}
}
return {};
}
namespace {
class XBufferedStream : public cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable>,
public comphelper::ByteReader
{
std::vector<sal_Int8> maBytes;
size_t mnPos;
size_t remainingSize() const
{
return maBytes.size() - mnPos;
}
bool hasBytes() const
{
return mnPos < maBytes.size();
}
public :
XBufferedStream( const uno::Reference<XInputStream>& xSrcStream ) : mnPos(0 )
{
sal_Int32 nRemaining = xSrcStream->available();
maBytes.reserve(nRemaining);
if (auto pByteReader = dynamic_cast < comphelper::ByteReader* >( xSrcStream.get() ))
{
maBytes.resize(nRemaining);
sal_Int8* pData = maBytes.data();
while (nRemaining > 0 )
{
sal_Int32 nRead = pByteReader->readSomeBytes(pData, nRemaining);
nRemaining -= nRead;
pData += nRead;
}
return ;
}
const sal_Int32 nBufSize = 8192 ;
uno::Sequence<sal_Int8> aBuf(nBufSize);
while (nRemaining > 0 )
{
const sal_Int32 nBytes = xSrcStream->readBytes(aBuf, std::min(nBufSize, nRemaining));
if (!nBytes)
break ;
maBytes.insert(maBytes.end(), aBuf.begin(), aBuf.begin() + nBytes);
nRemaining -= nBytes;
}
}
virtual sal_Int32 SAL_CALL readBytes( uno::Sequence<sal_Int8>& rData, sal_Int32 nBytesToRead ) override
{
if (!hasBytes())
return 0 ;
sal_Int32 nReadSize = std::min<sal_Int32>(nBytesToRead, remainingSize());
rData.realloc(nReadSize);
auto pData = rData.getArray();
std::vector<sal_Int8>::const_iterator it = maBytes.cbegin();
std::advance(it, mnPos);
for (sal_Int32 i = 0 ; i < nReadSize; ++i, ++it)
pData[i] = *it;
mnPos += nReadSize;
return nReadSize;
}
virtual sal_Int32 readSomeBytes(sal_Int8* pData, sal_Int32 nBytesToRead) override
{
if (!hasBytes())
return 0 ;
sal_Int32 nReadSize = std::min<sal_Int32>(nBytesToRead, remainingSize());
std::vector<sal_Int8>::const_iterator it = maBytes.cbegin();
std::advance(it, mnPos);
for (sal_Int32 i = 0 ; i < nReadSize; ++i, ++it)
pData[i] = *it;
mnPos += nReadSize;
return nReadSize;
}
virtual sal_Int32 SAL_CALL readSomeBytes( ::css::uno::Sequence<sal_Int8>& rData, sal_Int32 nMaxBytesToRead ) override
{
return readBytes(rData, nMaxBytesToRead);
}
virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override
{
if (!hasBytes())
return ;
mnPos += nBytesToSkip;
}
virtual sal_Int32 SAL_CALL available() override
{
if (!hasBytes())
return 0 ;
return remainingSize();
}
virtual void SAL_CALL closeInput() override
{
}
// XSeekable
virtual void SAL_CALL seek( sal_Int64 location ) override
{
if ( location < 0 || o3tl::make_unsigned(location) > maBytes.size() )
throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );
mnPos = location;
}
virtual sal_Int64 SAL_CALL getPosition() override
{
return mnPos;
}
virtual sal_Int64 SAL_CALL getLength() override
{
return maBytes.size();
}
};
}
uno::Reference< XInputStream > ZipFile::createStreamForZipEntry(
const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder,
ZipEntry const & rEntry,
const ::rtl::Reference< EncryptionData > &rData,
sal_Int8 nStreamMode,
::std::optional<sal_Int64> const oDecryptedSize,
const bool bUseBufferedStream,
const OUString& aMediaType )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
rtl::Reference< XUnbufferedStream > xSrcStream = new XUnbufferedStream(
m_xContext, aMutexHolder, rEntry, xStream, rData, nStreamMode, oDecryptedSize, aMediaType, bRecoveryMode);
if (!bUseBufferedStream)
return xSrcStream;
#ifndef EMSCRIPTEN
static const sal_Int32 nThreadingThreshold = 10000 ;
// "encrypted-package" is the only data stream, no point in threading it
if (nThreadingThreshold < xSrcStream->available()
&& rEntry.sPath != "encrypted-package"
// tdf#160888 no threading for AEAD streams:
// 1. the whole stream must be read immediately to verify tag
// 2. XBufferedThreadedStream uses same m_aMutexHolder->GetMutex()
// => caller cannot read without deadlock
&& (nStreamMode != UNBUFF_STREAM_DATA
|| !rData.is()
|| rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C))
{
return new XBufferedThreadedStream(xSrcStream, xSrcStream->getSize());
}
#endif
return new XBufferedStream(xSrcStream);
}
uno::Reference< XInputStream > ZipFile::StaticGetDataFromRawStream(
const rtl::Reference<comphelper::RefCountedMutex>& rMutexHolder,
const uno::Reference<uno::XComponentContext>& rxContext,
const uno::Reference<XInputStream>& xStream,
const ::rtl::Reference<EncryptionData> &rData)
{
if (!rData.is())
throw ZipIOException(u"Encrypted stream without encryption data!" _ustr );
if (!rData->m_aKey.hasElements())
throw packages::WrongPasswordException(THROW_WHERE);
uno::Reference<XSeekable> xSeek(xStream, UNO_QUERY);
if (!xSeek.is())
throw ZipIOException(u"The stream must be seekable!" _ustr);
// if we have a digest, then this file is an encrypted one and we should
// check if we can decrypt it or not
SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(),
"package" , "Can't detect password correctness without digest!" );
if (rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C)
{
// skip header
xSeek->seek(n_ConstHeaderSize + rData->m_aInitVector.getLength()
+ rData->m_aSalt.getLength() + rData->m_aDigest.getLength());
try
{ // XUnbufferedStream does not support XSeekable so wrap it
::rtl::Reference<XBufferedStream> const pRet(
new XBufferedStream(new XUnbufferedStream(rMutexHolder, xStream, rData)));
// currently XBufferedStream reads the whole stream in its ctor (to
// verify the tag) - in case this gets changed, explicitly seek here
pRet->seek(pRet->getLength());
pRet->seek(0 );
return pRet;
}
catch (uno::Exception const &)
{
throw packages::WrongPasswordException(THROW_WHERE);
}
}
else if (rData->m_aDigest.hasElements())
{
sal_Int32 nSize = sal::static_int_cast<sal_Int32>(xSeek->getLength());
if (nSize > n_ConstDigestLength + 32 )
nSize = n_ConstDigestLength + 32 ;
// skip header
xSeek->seek(n_ConstHeaderSize + rData->m_aInitVector.getLength() +
rData->m_aSalt.getLength() + rData->m_aDigest.getLength());
// Only want to read enough to verify the digest
Sequence<sal_Int8> aReadBuffer(nSize);
xStream->readBytes(aReadBuffer, nSize);
if (!StaticHasValidPassword(rxContext, aReadBuffer, rData))
throw packages::WrongPasswordException(THROW_WHERE);
}
return new XUnbufferedStream(rMutexHolder, xStream, rData);
}
ZipEnumeration ZipFile::entries()
{
return aEntries;
}
uno::Reference< XInputStream > ZipFile::getInputStream( ZipEntry& rEntry,
const ::rtl::Reference< EncryptionData > &rData,
::std::optional<sal_Int64> const oDecryptedSize,
const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
if ( rEntry.nOffset <= 0 )
readLOC( rEntry );
// We want to return a rawStream if we either don't have a key or if the
// key is wrong
bool bNeedRawStream = rEntry.nMethod == STORED;
if (oDecryptedSize && rData.is())
{
uno::Reference<XInputStream> const xRet(
checkValidPassword(rEntry, rData, *oDecryptedSize, aMutexHolder));
if (xRet.is())
{
return xRet;
}
bNeedRawStream = true ;
}
return createStreamForZipEntry ( aMutexHolder,
rEntry,
rData,
bNeedRawStream ? UNBUFF_STREAM_RAW : UNBUFF_STREAM_DATA,
oDecryptedSize);
}
uno::Reference< XInputStream > ZipFile::getDataStream( ZipEntry& rEntry,
const ::rtl::Reference< EncryptionData > &rData,
::std::optional<sal_Int64> const oDecryptedSize,
const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
if ( rEntry.nOffset <= 0 )
readLOC( rEntry );
// An exception must be thrown in case stream is encrypted and
// there is no key or the key is wrong
bool bNeedRawStream = false ;
if (oDecryptedSize)
{
// in case no digest is provided there is no way
// to detect password correctness
if ( !rData.is() )
throw ZipException(u"Encrypted stream without encryption data!" _ustr );
// if we have a digest, then this file is an encrypted one and we should
// check if we can decrypt it or not
SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(),
"package" , "Can't detect password correctness without digest!" );
uno::Reference<XInputStream> const xRet(checkValidPassword(rEntry, rData, *oDecryptedSize, aMutexHolder));
if (!xRet.is())
{
throw packages::WrongPasswordException(THROW_WHERE);
}
return xRet;
}
else
bNeedRawStream = ( rEntry.nMethod == STORED );
return createStreamForZipEntry ( aMutexHolder,
rEntry,
rData,
bNeedRawStream ? UNBUFF_STREAM_RAW : UNBUFF_STREAM_DATA,
oDecryptedSize);
}
uno::Reference< XInputStream > ZipFile::getRawData( ZipEntry& rEntry,
const ::rtl::Reference< EncryptionData >& rData,
::std::optional<sal_Int64> const oDecryptedSize,
const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder,
const bool bUseBufferedStream )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
if ( rEntry.nOffset <= 0 )
readLOC( rEntry );
return createStreamForZipEntry(aMutexHolder, rEntry, rData,
UNBUFF_STREAM_RAW, oDecryptedSize, bUseBufferedStream);
}
uno::Reference< XInputStream > ZipFile::getWrappedRawStream(
ZipEntry& rEntry,
const ::rtl::Reference< EncryptionData >& rData,
sal_Int64 const nDecryptedSize,
const OUString& aMediaType,
const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
if ( !rData.is() )
throw packages::NoEncryptionException(THROW_WHERE );
if ( rEntry.nOffset <= 0 )
readLOC( rEntry );
return createStreamForZipEntry(aMutexHolder, rEntry, rData,
UNBUFF_STREAM_WRAPPEDRAW, nDecryptedSize, true , aMediaType);
}
sal_uInt64 ZipFile::readLOC(ZipEntry &rEntry)
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
std::vector<sal_Int8> aNameBuffer;
std::vector<sal_Int8> aExtraBuffer;
return readLOC_Impl(rEntry, aNameBuffer, aExtraBuffer);
}
// Pass in a shared name buffer to reduce the number of allocations
// we do when reading the CEN.
sal_uInt64 ZipFile::readLOC_Impl(ZipEntry &rEntry, std::vector<sal_Int8>& rNameBuffer, std::vector<sal_Int8>& rExtraBuffer)
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
sal_Int64 nPos = -rEntry.nOffset;
aGrabber.seek(nPos);
std::array<sal_Int8, 30 > aHeader;
if (aGrabber.readBytes(aHeader.data(), 30 ) != 30 )
throw uno::RuntimeException();
MemoryByteGrabber headerMemGrabber(aHeader.data(), 30 );
sal_Int32 nTestSig = headerMemGrabber.ReadInt32();
if (nTestSig != LOCSIG)
throw ZipIOException(u"Invalid LOC header (bad signature)" _ustr );
// Ignore all (duplicated) information from the local file header.
// various programs produced "broken" zip files; even LO at some point.
// Just verify the path and calculate the data offset and otherwise
// rely on the central directory info.
// version - ignore any mismatch (Maven created JARs)
sal_uInt16 const nVersion = headerMemGrabber.ReadUInt16();
sal_uInt16 const nLocFlag = headerMemGrabber.ReadUInt16(); // general purpose bit flag
sal_uInt16 const nLocMethod = headerMemGrabber.ReadUInt16(); // compression method
// Do *not* compare timestamps, since MSO 2010 can produce documents
// with timestamp difference in the central directory entry and local
// file header.
headerMemGrabber.ReadInt32(); //time
sal_uInt32 nLocCrc = headerMemGrabber.ReadUInt32(); //crc
sal_uInt64 nLocCompressedSize = headerMemGrabber.ReadUInt32(); //compressed size
sal_uInt64 nLocSize = headerMemGrabber.ReadUInt32(); //size
sal_Int16 nPathLen = headerMemGrabber.ReadInt16();
sal_Int16 nExtraLen = headerMemGrabber.ReadInt16();
if (nPathLen < 0 )
{
SAL_WARN("package" , "bogus path len of: " << nPathLen);
nPathLen = 0 ;
}
rEntry.nOffset = aGrabber.getPosition() + nPathLen + nExtraLen;
sal_Int64 nEnd = {}; // avoid -Werror=maybe-uninitialized
bool bBroken = false ;
try
{
// read always in UTF8, some tools seem not to set UTF8 bit
// coverity[tainted_data] - we've checked negative lens, and up to max short is ok here
rNameBuffer.resize(nPathLen);
sal_Int32 nRead = aGrabber.readBytes(rNameBuffer.data(), nPathLen);
std::string_view aNameView(reinterpret_cast <const char *>(rNameBuffer.data()), nRead);
OUString sLOCPath( aNameView.data(), aNameView.size(), RTL_TEXTENCODING_UTF8 );
if ( rEntry.nPathLen == -1 ) // the file was created
{
rEntry.nPathLen = nPathLen;
rEntry.sPath = sLOCPath;
}
if (rEntry.nPathLen != nPathLen || rEntry.sPath != sLOCPath)
{
SAL_INFO("package" , "LOC inconsistent name: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
bool isZip64{false };
::std::optional<sal_uInt64> oOffset64;
if (nExtraLen != 0 )
{
rExtraBuffer.resize(nExtraLen);
aGrabber.readBytes(rExtraBuffer.data(), nExtraLen);
MemoryByteGrabber extraMemGrabber(rExtraBuffer.data(), nExtraLen);
isZip64 = readExtraFields(extraMemGrabber, nExtraLen,
nLocSize, nLocCompressedSize, oOffset64, &aNameView);
}
if (!isZip64 && 45 <= nVersion)
{
// for Excel compatibility, assume Zip64 - https://rzymek.github.io/post/excel-zip64/
isZip64 = true ;
}
// Just plain ignore bits 1 & 2 of the flag field - they are either
// purely informative, or even fully undefined (depending on method).
// Also ignore bit 11 ("Language encoding flag"): tdf125300.docx is
// example with mismatch - and the actual file names are compared in
// any case and required to be UTF-8.
if ((rEntry.nFlag & ~0 x806U) != (nLocFlag & ~0 x806U))
{
SAL_INFO("package" , "LOC inconsistent flag: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
// TODO: "older versions with encrypted streams write mismatching DEFLATE/STORE" ???
if (rEntry.nMethod != nLocMethod)
{
SAL_INFO("package" , "LOC inconsistent method: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
if (o3tl::checked_add<sal_Int64>(rEntry.nOffset, rEntry.nCompressedSize, nEnd))
{
throw ZipException(u"Integer-overflow" _ustr);
}
// read "data descriptor" - this can be 12, 16, 20, or 24 bytes in size
if ((rEntry.nFlag & 0 x08) != 0 )
{
#if 0
// Unfortunately every encrypted ODF package entry hits this,
// because ODF requires deflated entry with value STORED and OOo/LO
// has always written compressed streams with data descriptor.
// So it is checked later in ZipPackage::checkZipEntriesWithDD()
if (nLocMethod == STORED)
{
SAL_INFO("package" , "LOC STORED with data descriptor: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
else
#endif
{
decltype(nLocCrc) nDDCrc;
decltype(nLocCompressedSize) nDDCompressedSize;
decltype(nLocSize) nDDSize;
aGrabber.seek(aGrabber.getPosition() + rEntry.nCompressedSize);
sal_uInt32 nTemp = aGrabber.ReadUInt32();
if (nTemp == 0 x08074b50) // APPNOTE says PK78 is optional???
{
nDDCrc = aGrabber.ReadUInt32();
}
else
{
nDDCrc = nTemp;
}
if (isZip64)
{
nDDCompressedSize = aGrabber.ReadUInt64();
nDDSize = aGrabber.ReadUInt64();
}
else
{
nDDCompressedSize = aGrabber.ReadUInt32();
nDDSize = aGrabber.ReadUInt32();
}
if (nEnd < aGrabber.getPosition())
{
nEnd = aGrabber.getPosition();
}
else
{
SAL_INFO("package" , "LOC invalid size: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
// tdf91429.docx has same values in LOC and in (superfluous) DD
if ((nLocCrc == 0 || nLocCrc == nDDCrc)
&& (nLocCompressedSize == 0 || nLocCompressedSize == sal_uInt64(-1 ) || nLocCompressedSize == nDDCompressedSize)
&& (nLocSize == 0 || nLocSize == sal_uInt64(-1 ) || nLocSize == nDDSize))
{
nLocCrc = nDDCrc;
nLocCompressedSize = nDDCompressedSize;
nLocSize = nDDSize;
}
else
{
SAL_INFO("package" , "LOC non-0 with data descriptor: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
}
}
// unit test file export64.zip has nLocCrc/nLocCS/nLocSize = 0 on mimetype
if (nLocCrc != 0 && static_cast <sal_uInt32>(rEntry.nCrc) != nLocCrc)
{
SAL_INFO("package" , "LOC inconsistent CRC: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
if (nLocCompressedSize != 0 && static_cast <sal_uInt64>(rEntry.nCompressedSize) != nLocCompressedSize)
{
SAL_INFO("package" , "LOC inconsistent compressed size: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
if (nLocSize != 0 && static_cast <sal_uInt64>(rEntry.nSize) != nLocSize)
{
SAL_INFO("package" , "LOC inconsistent size: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
if (oOffset64 && o3tl::make_unsigned(nPos) != *oOffset64)
{
SAL_INFO("package" , "LOC inconsistent offset: \" " << rEntry.sPath << " \"" );
bBroken = true ;
}
}
catch (...)
{
bBroken = true ;
}
if ( bBroken && !bRecoveryMode )
throw ZipIOException(u"The stream seems to be broken!" _ustr );
return nEnd;
}
std::tuple<sal_Int64, sal_Int64, sal_Int64> ZipFile::findCentralDirectory()
{
// this method is called in constructor only, no need for mutex
try
{
sal_Int64 const nLength = aGrabber.getLength();
if (nLength < ENDHDR)
{
throw ZipException(u"Zip too small!" _ustr);
}
sal_Int64 nPos = nLength - ENDHDR - ZIP_MAXNAMELEN;
sal_Int64 nEnd = nPos >= 0 ? nPos : 0 ;
aGrabber.seek( nEnd );
auto nSize = nLength - nEnd;
std::unique_ptr<sal_Int8[]> aBuffer(new sal_Int8[nSize]);
if (nSize != aGrabber.readBytes(aBuffer.get(), nSize))
throw ZipException(u"Zip END signature not found!" _ustr );
const sal_Int8 *pBuffer = aBuffer.get();
sal_Int64 nEndPos = {};
nPos = nSize - ENDHDR;
while ( nPos >= 0 )
{
if (pBuffer[nPos] == 'P' && pBuffer[nPos+1 ] == 'K' && pBuffer[nPos+2 ] == 5 && pBuffer[nPos+3 ] == 6 )
{
nEndPos = nPos + nEnd;
break ;
}
if (nPos == 0 )
{
throw ZipException(u"Zip END signature not found!" _ustr);
}
nPos--;
}
aGrabber.seek(nEndPos + 4 );
sal_uInt16 const nEndDisk = aGrabber.ReadUInt16();
if (nEndDisk != 0 && nEndDisk != 0 xFFFF)
{ // only single disk is supported!
throw ZipException(u"invalid end (disk)" _ustr );
}
sal_uInt16 const nEndDirDisk = aGrabber.ReadUInt16();
if (nEndDirDisk != 0 && nEndDisk != 0 xFFFF)
{
throw ZipException(u"invalid end (directory disk)" _ustr );
}
sal_uInt16 const nEndDiskEntries = aGrabber.ReadUInt16();
sal_uInt16 const nEndEntries = aGrabber.ReadUInt16();
if (nEndDiskEntries != nEndEntries)
{
throw ZipException(u"invalid end (entries)" _ustr );
}
sal_Int32 const nEndDirSize = aGrabber.ReadInt32();
sal_Int32 const nEndDirOffset = aGrabber.ReadInt32();
// Zip64 end of central directory locator must immediately precede
// end of central directory record
if (20 <= nEndPos)
{
aGrabber.seek(nEndPos - 20 );
std::array<sal_Int8, 20 > aZip64EndLocator;
if (20 != aGrabber.readBytes(aZip64EndLocator.data(), 20 ))
throw uno::RuntimeException();
MemoryByteGrabber loc64Grabber(aZip64EndLocator.data(), 20 );
if (loc64Grabber.ReadUInt8() == 'P'
&& loc64Grabber.ReadUInt8() == 'K'
&& loc64Grabber.ReadUInt8() == 6
&& loc64Grabber.ReadUInt8() == 7 )
{
sal_uInt32 const nLoc64Disk = loc64Grabber.ReadUInt32();
if (nLoc64Disk != 0 )
{
throw ZipException(u"invalid Zip64 end locator (disk)" _ustr);
}
sal_Int64 const nLoc64End64Offset = loc64Grabber.ReadUInt64();
if (nEndPos < 20 + 56 || (nEndPos - 20 - 56 ) < nLoc64End64Offset
|| nLoc64End64Offset < 0 )
{
throw ZipException(u"invalid Zip64 end locator (offset)" _ustr);
}
sal_uInt32 const nLoc64Disks = loc64Grabber.ReadUInt32();
if (nLoc64Disks != 1 )
{
throw ZipException(u"invalid Zip64 end locator (number of disks)" _ustr);
}
aGrabber.seek(nLoc64End64Offset);
std::vector<sal_Int8> aZip64EndDirectory(nEndPos - 20 - nLoc64End64Offset);
aGrabber.readBytes(aZip64EndDirectory.data(), nEndPos - 20 - nLoc64End64Offset);
MemoryByteGrabber end64Grabber(aZip64EndDirectory.data(), nEndPos - 20 - nLoc64End64Offset);
if (end64Grabber.ReadUInt8() != 'P'
|| end64Grabber.ReadUInt8() != 'K'
|| end64Grabber.ReadUInt8() != 6
|| end64Grabber.ReadUInt8() != 6 )
{
throw ZipException(u"invalid Zip64 end (signature)" _ustr);
}
sal_Int64 const nEnd64Size = end64Grabber.ReadUInt64();
if (nEnd64Size != nEndPos - 20 - nLoc64End64Offset - 12 )
{
throw ZipException(u"invalid Zip64 end (size)" _ustr);
}
end64Grabber.ReadUInt16(); // ignore version made by
end64Grabber.ReadUInt16(); // ignore version needed to extract
sal_uInt32 const nEnd64Disk = end64Grabber.ReadUInt32();
if (nEnd64Disk != 0 )
{
throw ZipException(u"invalid Zip64 end (disk)" _ustr);
}
sal_uInt32 const nEnd64EndDisk = end64Grabber.ReadUInt32();
if (nEnd64EndDisk != 0 )
{
throw ZipException(u"invalid Zip64 end (directory disk)" _ustr);
}
sal_uInt64 const nEnd64DiskEntries = end64Grabber.ReadUInt64();
sal_uInt64 const nEnd64Entries = end64Grabber.ReadUInt64();
if (nEnd64DiskEntries != nEnd64Entries)
{
throw ZipException(u"invalid Zip64 end (entries)" _ustr);
}
sal_Int64 const nEnd64DirSize = end64Grabber.ReadUInt64();
sal_Int64 const nEnd64DirOffset = end64Grabber.ReadUInt64();
if (nEndEntries != sal_uInt16(-1 ) && nEnd64Entries != nEndEntries)
{
throw ZipException(u"inconsistent Zip/Zip64 end (entries)" _ustr);
}
if (nEndDirSize != -1
&& nEnd64DirSize != nEndDirSize)
{
throw ZipException(u"inconsistent Zip/Zip64 end (size)" _ustr);
}
if (nEndDirOffset != -1
&& nEnd64DirOffset != nEndDirOffset)
{
throw ZipException(u"inconsistent Zip/Zip64 end (offset)" _ustr);
}
sal_Int64 end;
if (o3tl::checked_add<sal_Int64>(nEnd64DirOffset, nEnd64DirSize, end)
|| nLoc64End64Offset < end
|| nEnd64DirOffset < 0
|| nLoc64End64Offset - nEnd64DirSize != nEnd64DirOffset)
{
throw ZipException(u"Invalid Zip64 end (bad central directory size)" _ustr);
}
return { nEnd64Entries, nEnd64DirSize, nEnd64DirOffset };
}
}
sal_Int32 end;
if (o3tl::checked_add<sal_Int32>(nEndDirOffset, nEndDirSize, end)
|| nEndPos < end
|| nEndDirOffset < 0
|| nEndPos - nEndDirSize != nEndDirOffset)
{
throw ZipException(u"Invalid END header (bad central directory size)" _ustr);
}
return { nEndEntries, nEndDirSize, nEndDirOffset };
}
catch ( IllegalArgumentException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
catch ( NotConnectedException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
catch ( BufferSizeExceededException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
}
sal_Int32 ZipFile::readCEN()
{
// this method is called in constructor only, no need for mutex
sal_Int32 nCenPos = -1 ;
try
{
auto [nTotal, nCenLen, nCenOff] = findCentralDirectory();
nCenPos = nCenOff; // data before start of zip is not supported
if ( nTotal > ZIP_MAXENTRIES )
throw ZipException(u"too many entries in ZIP File" _ustr );
if (nCenLen < nTotal * CENHDR) // prevent overflow with ZIP_MAXENTRIES
throw ZipException(u"invalid END header (bad entry count)" _ustr );
if (nCenLen > SAL_MAX_INT32 || nCenLen < 0 )
throw ZipException(u"central directory too big" _ustr);
aGrabber.seek(nCenPos);
std::vector<sal_Int8> aCENBuffer(nCenLen);
sal_Int64 nRead = aGrabber.readBytes ( aCENBuffer.data(), nCenLen );
if (nCenLen != nRead)
throw ZipException (u"Error reading CEN into memory buffer!" _ustr );
MemoryByteGrabber aMemGrabber(aCENBuffer.data(), nCenLen);
ZipEntry aEntry;
sal_Int16 nCommentLen;
::std::vector<std::pair<sal_uInt64, sal_uInt64>> unallocated = { { 0 , nCenPos } };
aEntries.reserve(nTotal);
sal_Int64 nCount;
std::vector<sal_Int8> aTempNameBuffer;
std::vector<sal_Int8> aTempExtraBuffer;
for (nCount = 0 ; nCount < nTotal; nCount++)
{
sal_Int32 nTestSig = aMemGrabber.ReadInt32();
if ( nTestSig != CENSIG )
throw ZipException(u"Invalid CEN header (bad signature)" _ustr );
sal_uInt16 versionMadeBy = aMemGrabber.ReadUInt16();
aEntry.nVersion = aMemGrabber.ReadInt16();
aEntry.nFlag = aMemGrabber.ReadInt16();
if ( ( aEntry.nFlag & 1 ) == 1 )
throw ZipException(u"Invalid CEN header (encrypted entry)" _ustr );
aEntry.nMethod = aMemGrabber.ReadInt16();
if ( aEntry.nMethod != STORED && aEntry.nMethod != DEFLATED)
throw ZipException(u"Invalid CEN header (bad compression method)" _ustr );
aEntry.nTime = aMemGrabber.ReadInt32();
aEntry.nCrc = aMemGrabber.ReadInt32();
sal_uInt64 nCompressedSize = aMemGrabber.ReadUInt32();
sal_uInt64 nSize = aMemGrabber.ReadUInt32();
aEntry.nPathLen = aMemGrabber.ReadInt16();
aEntry.nExtraLen = aMemGrabber.ReadInt16();
nCommentLen = aMemGrabber.ReadInt16();
aMemGrabber.skipBytes ( 4 );
sal_uInt32 externalFileAttributes = aMemGrabber.ReadUInt32();
sal_uInt64 nOffset = aMemGrabber.ReadUInt32();
if ( aEntry.nPathLen < 0 )
throw ZipException(u"unexpected name length" _ustr );
if ( nCommentLen < 0 )
throw ZipException(u"unexpected comment length" _ustr );
if ( aEntry.nExtraLen < 0 )
throw ZipException(u"unexpected extra header info length" _ustr );
if (aEntry.nPathLen > aMemGrabber.remainingSize())
throw ZipException(u"name too long" _ustr);
// read always in UTF8, some tools seem not to set UTF8 bit
std::string_view aPathView(reinterpret_cast <char const *>(aMemGrabber.getCurrentPos()), aEntry.nPathLen);
aEntry.sPath = OUString( aPathView.data(), aPathView.size(), RTL_TEXTENCODING_UTF8 );
if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aEntry.sPath, true ) )
throw ZipException(u"Zip entry has an invalid name." _ustr );
aMemGrabber.skipBytes(aEntry.nPathLen);
if (aEntry.nExtraLen>0 )
{
::std::optional<sal_uInt64> oOffset64;
readExtraFields(aMemGrabber, aEntry.nExtraLen, nSize, nCompressedSize, oOffset64, &aPathView);
if (oOffset64)
{
nOffset = *oOffset64;
}
}
aEntry.nCompressedSize = nCompressedSize;
aEntry.nSize = nSize;
aEntry.nOffset = nOffset;
if (o3tl::checked_multiply<sal_Int64>(aEntry.nOffset, -1 , aEntry.nOffset))
throw ZipException(u"Integer-overflow" _ustr);
if (aEntry.nMethod == STORED && aEntry.nCompressedSize != aEntry.nSize)
{
throw ZipException(u"entry STORED with inconsistent size" _ustr);
}
aMemGrabber.skipBytes(nCommentLen);
// unfortunately readLOC is required now to check the consistency
assert(aEntry.nOffset <= 0 );
sal_uInt64 const nStart{ o3tl::make_unsigned(-aEntry.nOffset) };
sal_uInt64 const nEnd = readLOC_Impl(aEntry, aTempNameBuffer, aTempExtraBuffer);
assert(nStart < nEnd);
for (auto it = unallocated.begin(); ; ++it)
{
if (it == unallocated.end())
{
throw ZipException(u"overlapping entries" _ustr);
}
if (nStart < it->first)
{
throw ZipException(u"overlapping entries" _ustr);
}
else if (it->first == nStart)
{
if (it->second == nEnd)
{
unallocated.erase(it);
break ;
}
else if (nEnd < it->second)
{
it->first = nEnd;
break ;
}
else
{
throw ZipException(u"overlapping entries" _ustr);
}
}
else if (nStart < it->second)
{
if (nEnd < it->second)
{
auto const temp{it->first};
it->first = nEnd;
unallocated.insert(it, { temp, nStart });
break ;
}
else if (nEnd == it->second)
{
it->second = nStart;
break ;
}
else
{
throw ZipException(u"overlapping entries" _ustr);
}
}
}
// Is this a FAT-compatible empty entry?
if (aEntry.nSize == 0 && (versionMadeBy & 0 xff00) == 0 )
{
constexpr sal_uInt32 FILE_ATTRIBUTE_DIRECTORY = 16 ;
if (externalFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue ; // This is a directory entry, not a stream - skip it
}
if (aEntries.find(aEntry.sPath) != aEntries.end())
{
SAL_INFO("package" , "Duplicate CEN entry: \" " << aEntry.sPath << " \"" );
throw ZipException(u"Duplicate CEN entry" _ustr);
}
if (aEntries.empty() && m_Checks == Checks::TryCheckInsensitive)
{
if (aEntry.sPath == "mimetype" && aEntry.nSize == 0 )
{ // tdf#162866 AutoCorrect uses ODF package, directories are
m_Checks = Checks::Default ; // user-defined => ignore!
}
else
{
m_Checks = Checks::CheckInsensitive;
}
}
// this is required for OOXML, but not for ODF
auto const lowerPath(aEntry.sPath.toAsciiLowerCase());
if (!m_EntriesInsensitive.insert(lowerPath).second && m_Checks == Checks::CheckInsensitive)
{
SAL_INFO("package" , "Duplicate CEN entry (case insensitive): \" " << aEntry.sPath << " \"" );
throw ZipException(u"Duplicate CEN entry (case insensitive)" _ustr);
}
aEntries[aEntry.sPath] = aEntry;
}
if (nCount != nTotal)
throw ZipException(u"Count != Total" _ustr );
if (!unallocated.empty())
{
if (std::all_of(unallocated.begin(), unallocated.end(), [](auto const & it) {
return it.second - it.first == 12 || it.second - it.first == 16 ;
}))
{
throw ZipException(u"Zip file has holes the size of data descriptors; producer forgot to set flag bit 3?" _ustr);
}
throw ZipException(u"Zip file has holes! It will leak!" _ustr);
}
}
catch ( IllegalArgumentException & )
{
// seek can throw this...
nCenPos = -1 ; // make sure we return -1 to indicate an error
}
return nCenPos;
}
bool ZipFile::readExtraFields(MemoryByteGrabber& aMemGrabber, sal_Int16 nExtraLen,
sal_uInt64& nSize, sal_uInt64& nCompressedSize,
std::optional<sal_uInt64> & roOffset,
std::string_view const * pCENFilenameToCheck)
{
bool isZip64{false };
while (nExtraLen > 0 ) // Extensible data fields
{
sal_Int16 nheaderID = aMemGrabber.ReadInt16();
sal_uInt16 dataSize = aMemGrabber.ReadUInt16();
if (nheaderID == 1 ) // Load Zip64 Extended Information Extra Field
{
// Datasize should be 28byte but some files have less (maybe non standard?)
nSize = aMemGrabber.ReadUInt64();
sal_uInt16 nReadSize = 8 ;
if (dataSize >= 16 )
{
nCompressedSize = aMemGrabber.ReadUInt64();
nReadSize = 16 ;
if (dataSize >= 24 )
{
roOffset.emplace(aMemGrabber.ReadUInt64());
nReadSize = 24 ;
// 4 byte should be "Disk Start Number" but we not need it
}
}
if (dataSize > nReadSize)
aMemGrabber.skipBytes(dataSize - nReadSize);
isZip64 = true ;
}
// Info-ZIP Unicode Path Extra Field - pointless as we expect UTF-8 in CEN already
else if (nheaderID == 0 x7075 && pCENFilenameToCheck) // ignore in recovery mode
{
if (aMemGrabber.remainingSize() < dataSize)
{
SAL_INFO("package" , "Invalid Info-ZIP Unicode Path Extra Field: invalid TSize" );
throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field" _ustr);
}
auto const nVersion = aMemGrabber.ReadUInt8();
if (nVersion != 1 )
{
SAL_INFO("package" , "Invalid Info-ZIP Unicode Path Extra Field: unexpected Version" );
throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field" _ustr);
}
// this CRC32 is actually of the pCENFilenameToCheck
// so it's pointless to check it if we require the UnicodeName
// to be equal to the CEN name anyway (and pCENFilenameToCheck
// is already converted to UTF-16 here)
(void ) aMemGrabber.ReadUInt32();
// this is required to be UTF-8
std::string_view unicodePath(reinterpret_cast <char const *>(aMemGrabber.getCurrentPos()),
dataSize - 5 );
aMemGrabber.skipBytes(dataSize - 5 );
if (unicodePath != *pCENFilenameToCheck)
{
SAL_INFO("package" , "Invalid Info-ZIP Unicode Path Extra Field: unexpected UnicodeName" );
throw ZipException(u"Invalid Info-ZIP Unicode Path Extra Field" _ustr);
}
}
else
{
aMemGrabber.skipBytes(dataSize);
}
nExtraLen -= dataSize + 4 ;
}
return isZip64;
}
// PK34: Local file header
bool ZipFile::HandlePK34(std::span<const sal_Int8> data, sal_Int64 dataOffset, sal_Int64 totalSize)
{
ZipEntry aEntry;
Sequence<sal_Int8> aTmpBuffer(data.data() + 4 , 26 );
MemoryByteGrabber aMemGrabber(aTmpBuffer);
aEntry.nVersion = aMemGrabber.ReadInt16();
aEntry.nFlag = aMemGrabber.ReadInt16();
if ((aEntry.nFlag & 1 ) == 1 )
return false ;
aEntry.nMethod = aMemGrabber.ReadInt16();
if (aEntry.nMethod != STORED && aEntry.nMethod != DEFLATED)
return false ;
aEntry.nTime = aMemGrabber.ReadInt32();
aEntry.nCrc = aMemGrabber.ReadInt32();
sal_uInt64 nCompressedSize = aMemGrabber.ReadUInt32();
sal_uInt64 nSize = aMemGrabber.ReadUInt32();
aEntry.nPathLen = aMemGrabber.ReadInt16();
aEntry.nExtraLen = aMemGrabber.ReadInt16();
const sal_Int32 nDescrLength = (aEntry.nMethod == DEFLATED && (aEntry.nFlag & 8 )) ? 16 : 0 ;
const sal_Int64 nBlockHeaderLength = aEntry.nPathLen + aEntry.nExtraLen + 30 + nDescrLength;
if (aEntry.nPathLen < 0 || aEntry.nExtraLen < 0 || dataOffset + nBlockHeaderLength > totalSize)
return false ;
// read always in UTF8, some tools seem not to set UTF8 bit
if (o3tl::make_unsigned(30 + aEntry.nPathLen) <= data.size())
aEntry.sPath = OUString(reinterpret_cast <char const *>(data.data() + 30 ), aEntry.nPathLen,
RTL_TEXTENCODING_UTF8);
else
{
std::vector<sal_Int8> aFileName(aEntry.nPathLen);
aGrabber.seek(dataOffset + 30 );
aEntry.nPathLen = aGrabber.readBytes(aFileName.data(), aEntry.nPathLen);
aEntry.sPath = OUString(reinterpret_cast <const char *>(aFileName.data()),
aEntry.nPathLen, RTL_TEXTENCODING_UTF8);
}
aEntry.sPath = aEntry.sPath.replace('\\' , '/' );
// read 64bit header
if (aEntry.nExtraLen > 0 )
{
std::vector<sal_Int8> aExtraBuffer(aEntry.nExtraLen);
if (o3tl::make_unsigned(30 + aEntry.nPathLen) + aEntry.nExtraLen <= data.size())
{
auto it = data.begin() + 30 + aEntry.nPathLen;
std::copy(it, it + aEntry.nExtraLen, aExtraBuffer.begin());
}
else
{
aGrabber.seek(dataOffset + 30 + aEntry.nExtraLen);
aGrabber.readBytes(aExtraBuffer.data(), aEntry.nExtraLen);
}
MemoryByteGrabber aMemGrabberExtra(aExtraBuffer.data(), aEntry.nExtraLen);
if (aEntry.nExtraLen > 0 )
{
::std::optional<sal_uInt64> oOffset64;
readExtraFields(aMemGrabberExtra, aEntry.nExtraLen, nSize, nCompressedSize, oOffset64, nullptr);
}
}
sal_Int64 nDataSize = (aEntry.nMethod == DEFLATED) ? nCompressedSize : nSize;
sal_Int64 nBlockLength = nDataSize + nBlockHeaderLength;
if (dataOffset + nBlockLength > totalSize)
return false ;
aEntry.nCompressedSize = nCompressedSize;
aEntry.nSize = nSize;
aEntry.nOffset = dataOffset + 30 + aEntry.nPathLen + aEntry.nExtraLen;
if ((aEntry.nSize || aEntry.nCompressedSize) && !checkSizeAndCRC(aEntry))
{
aEntry.nCrc = 0 ;
aEntry.nCompressedSize = 0 ;
aEntry.nSize = 0 ;
}
// Do not add this entry, if it is empty and is a directory of an already existing entry
if (aEntry.nSize == 0 && aEntry.nCompressedSize == 0
&& std::find_if(aEntries.begin(), aEntries.end(),
[path = OUString(aEntry.sPath + "/" )](const auto & r)
{ return r.first.startsWith(path); })
!= aEntries.end())
return false ;
auto const lowerPath(aEntry.sPath.toAsciiLowerCase());
if (m_EntriesInsensitive.find(lowerPath) != m_EntriesInsensitive.end())
{ // this is required for OOXML, but not for ODF
return false ;
}
m_EntriesInsensitive.insert(lowerPath);
aEntries.emplace(aEntry.sPath, aEntry);
// Drop any "directory" entry corresponding to this one's path; since we don't use
// central directory, we don't see external file attributes, so sanitize here
sal_Int32 i = 0 ;
for (OUString subdir = aEntry.sPath.getToken(0 , '/' , i); i >= 0 ;
subdir += OUString::Concat("/" ) + o3tl::getToken(aEntry.sPath, 0 , '/' , i))
{
if (auto it = aEntries.find(subdir); it != aEntries.end())
{
// if not empty, let it fail later in ZipPackage::getZipFileContents
if (it->second.nSize == 0 && it->second.nCompressedSize == 0 )
aEntries.erase(it);
}
}
return (aEntry.nFlag & 8 ) && (aEntry.nCompressedSize == 0 );
}
// PK78: Data descriptor
void ZipFile::HandlePK78(std::span<const sal_Int8> data, sal_Int64 dataOffset)
{
sal_Int64 nCompressedSize, nSize;
Sequence<sal_Int8> aTmpBuffer(data.data() + 4 , 12 + 8 + 4 );
MemoryByteGrabber aMemGrabber(aTmpBuffer);
sal_Int32 nCRC32 = aMemGrabber.ReadInt32();
// FIXME64: find a better way to recognize if Zip64 mode is used
// Now we check if the memory at +16 byte seems to be a signature
// if not, then probably Zip64 mode is used here, except
// if memory at +24 byte seems not to be a signature.
// Normally Data Descriptor should followed by the next Local File header
// that should start with PK34, except for the last file, then it may
// followed by Central directory that start with PK12, or
// followed by "archive decryption header" that don't have a signature.
if ((data[16 ] == 'P' && data[17 ] == 'K' && data[19 ] == data[18 ] + 1
&& (data[18 ] == 3 || data[18 ] == 1 ))
|| !(data[24 ] == 'P' && data[25 ] == 'K' && data[27 ] == data[26 ] + 1
&& (data[26 ] == 3 || data[26 ] == 1 )))
{
nCompressedSize = aMemGrabber.ReadUInt32();
nSize = aMemGrabber.ReadUInt32();
}
else
{
nCompressedSize = aMemGrabber.ReadUInt64();
nSize = aMemGrabber.ReadUInt64();
}
TryDDImpl(dataOffset, nCRC32, nCompressedSize, nSize);
}
bool ZipFile::TryDDImpl(sal_Int64 const dataOffset, sal_Int32 const nCRC32,
sal_Int64 const nCompressedSize, sal_Int64 const nSize)
{
for (auto & rEntry : aEntries)
{
// this is a broken package, accept this block not only for DEFLATED streams
if ((rEntry.second.nFlag & 8 ) == 0 )
continue ;
sal_Int64 nStreamOffset = dataOffset - nCompressedSize;
if (nStreamOffset == rEntry.second.nOffset
&& nCompressedSize > rEntry.second.nCompressedSize)
{
// only DEFLATED blocks need to be checked
bool bAcceptBlock = (rEntry.second.nMethod == STORED && nCompressedSize == nSize);
if (!bAcceptBlock)
{
sal_Int64 nRealSize = 0 ;
sal_Int32 nRealCRC = 0 ;
getSizeAndCRC(nStreamOffset, nCompressedSize, &nRealSize, &nRealCRC);
bAcceptBlock = (nRealSize == nSize && nRealCRC == nCRC32);
}
if (bAcceptBlock)
{
rEntry.second.nCrc = nCRC32;
rEntry.second.nCompressedSize = nCompressedSize;
rEntry.second.nSize = nSize;
return true ;
}
}
#if 0
// for now ignore clearly broken streams
else if ( !rEntry.second.nCompressedSize )
{
rEntry.second.nCrc = nCRC32;
sal_Int32 nRealStreamSize = dataOffset - rEntry.second.nOffset;
rEntry.second.nCompressedSize = nRealStreamSize;
rEntry.second.nSize = nSize;
}
#endif
}
return false ;
}
bool ZipFile::TryDDEndAt(std::span<const sal_Int8> const data, sal_Int64 const dataOffset)
{
assert(!aEntries.empty()); // HandlePK34 must ensure this
Sequence<sal_Int8> const buf32{data.data() + 8 , 12 };
MemoryByteGrabber mbg32{buf32};
sal_uInt32 const nCrc32{mbg32.ReadUInt32()};
sal_uInt32 const nCompressedSize32{mbg32.ReadUInt32()};
sal_uInt32 const nSize32{mbg32.ReadUInt32()};
if (TryDDImpl(dataOffset + 8 , nCrc32, nCompressedSize32, nSize32))
{
return true ;
}
// then try if Zip64 crc/sizes are plausible
Sequence<sal_Int8> const buf64{data.data(), 20 };
MemoryByteGrabber mbg64{buf64};
sal_uInt32 const nCrc64{mbg64.ReadUInt32()};
sal_uInt64 const nCompressedSize64{mbg64.ReadUInt64()};
sal_uInt64 const nSize64{mbg64.ReadUInt64()};
return TryDDImpl(dataOffset, nCrc64, nCompressedSize64, nSize64);
}
void ZipFile::recover()
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
const sal_Int64 nToRead = 32000 ;
std::vector<sal_Int8> aBuffer(nToRead);
try
{
const sal_Int64 nLength = aGrabber.getLength();
if (nLength < ENDHDR)
return ;
aGrabber.seek( 0 );
bool findDD{false };
sal_Int32 nRead;
for ( sal_Int64 nGenPos = 0 ; (nRead = aGrabber.readBytes( aBuffer.data(), nToRead )) && nRead > 16 ; )
{
const sal_Int8 *pBuffer = aBuffer.data();
const sal_Int32 nBufSize = nRead;
sal_Int64 nPos = 0 ;
// the buffer should contain at least one header,
// or if it is end of the file, at least the postheader with sizes and hash
while ( nPos < nBufSize - 30
|| ( nBufSize < nToRead && nPos < nBufSize - 16 ) )
{
if (pBuffer[nPos] == 'P' && pBuffer[nPos+1 ] == 'K' )
{
if (pBuffer[nPos+2 ] == 7 && pBuffer[nPos+3 ] == 8 )
{
findDD = false ;
HandlePK78(std::span(pBuffer + nPos, nBufSize - nPos), nGenPos + nPos);
nPos += 4 ;
}
else
{
if (findDD && 30 < nGenPos+nPos
&& TryDDEndAt(std::span(pBuffer + nPos - 20 , 20 ), nGenPos + nPos - 20 ))
{
findDD = false ;
}
if (nPos < nBufSize - 30
&& pBuffer[nPos+2 ] == 3 && pBuffer[nPos+3 ] == 4 )
{
findDD = HandlePK34(std::span(pBuffer + nPos, nBufSize - nPos), nGenPos + nPos, nLength);
nPos += 4 ;
}
else
{
++nPos;
}
}
}
else
nPos++;
}
nGenPos += nPos;
aGrabber.seek( nGenPos );
}
}
catch ( IllegalArgumentException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
catch ( NotConnectedException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
catch ( BufferSizeExceededException& )
{
throw ZipException(u"Zip END signature not found!" _ustr );
}
}
bool ZipFile::checkSizeAndCRC( const ZipEntry& aEntry )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
try
{
sal_Int32 nCRC = 0 ;
sal_Int64 nSize = 0 ;
if ( aEntry.nMethod == STORED )
return ( getCRC( aEntry.nOffset, aEntry.nSize ) == aEntry.nCrc );
if (aEntry.nCompressedSize < 0 )
{
SAL_WARN("package" , "bogus compressed size of: " << aEntry.nCompressedSize);
return false ;
}
getSizeAndCRC( aEntry.nOffset, aEntry.nCompressedSize, &nSize, &nCRC );
return ( aEntry.nSize == nSize && aEntry.nCrc == nCRC );
}
catch (uno::Exception const &)
{
return false ;
}
}
sal_Int32 ZipFile::getCRC( sal_Int64 nOffset, sal_Int64 nSize )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
CRC32 aCRC;
sal_Int64 nBlockSize = ::std::min(nSize, static_cast < sal_Int64 >(32000 ));
std::vector<sal_Int8> aBuffer(nBlockSize);
aGrabber.seek( nOffset );
sal_Int64 nRead = 0 ;
while (nRead < nSize)
{
sal_Int64 nToRead = std::min(nSize - nRead, nBlockSize);
sal_Int64 nReadThisTime = aGrabber.readBytes(aBuffer.data(), nToRead);
aCRC.updateSegment(aBuffer.data(), nReadThisTime);
nRead += nReadThisTime;
}
return aCRC.getValue();
}
void ZipFile::getSizeAndCRC( sal_Int64 nOffset, sal_Int64 nCompressedSize, sal_Int64 *nSize, sal_Int32 *nCRC )
{
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
CRC32 aCRC;
sal_Int64 nRealSize = 0 ;
ZipUtils::InflaterBytesZlib aInflaterLocal;
sal_Int32 nBlockSize = static_cast < sal_Int32 > (::std::min( nCompressedSize, static_cast < sal_Int64 >( 32000 ) ) );
std::vector < sal_Int8 > aBuffer(nBlockSize);
std::vector< sal_Int8 > aData( nBlockSize );
aGrabber.seek( nOffset );
sal_Int32 nRead;
for ( sal_Int64 ind = 0 ;
!aInflaterLocal.finished()
&& (nRead = aGrabber.readBytes( aBuffer.data(), nBlockSize ))
&& ind * nBlockSize < nCompressedSize;
ind++ )
{
sal_Int32 nLastInflated = 0 ;
sal_Int64 nInBlock = 0 ;
aInflaterLocal.setInput( aBuffer.data(), nRead );
do
{
nLastInflated = aInflaterLocal.doInflateSegment( aData.data(), nBlockSize, 0 , nBlockSize );
aCRC.updateSegment( aData.data(), nLastInflated );
nInBlock += nLastInflated;
} while ( !aInflaterLocal.finished() && nLastInflated );
nRealSize += nInBlock;
}
*nSize = nRealSize;
*nCRC = aCRC.getValue();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Messung V0.5 in Prozent C=91 H=97 G=93
¤ Dauer der Verarbeitung: 0.31 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland