diff options
Diffstat (limited to 'package/source/zipapi/ZipFile.cxx')
-rw-r--r-- | package/source/zipapi/ZipFile.cxx | 1474 |
1 files changed, 1474 insertions, 0 deletions
diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx new file mode 100644 index 0000000000..474b73ff53 --- /dev/null +++ b/package/source/zipapi/ZipFile.cxx @@ -0,0 +1,1474 @@ +/* -*- 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 <sal/log.hxx> +#include <o3tl/safeint.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> + +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 ) +: m_aMutexHolder(std::move( aMutexHolder )) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext (std::move( xContext )) +, bRecoveryMode( false ) +{ + if (bInitialise && readCEN() == -1 ) + { + aEntries.clear(); + throw ZipException( "stream data looks to be broken" ); + } +} + +ZipFile::ZipFile( rtl::Reference< comphelper::RefCountedMutex > aMutexHolder, + uno::Reference < XInputStream > const &xInput, + uno::Reference < XComponentContext > xContext, + bool bInitialise, bool bForceRecovery) +: m_aMutexHolder(std::move( aMutexHolder )) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext (std::move( xContext )) +, bRecoveryMode( bForceRecovery ) +{ + if (bInitialise) + { + if ( bForceRecovery ) + { + recover(); + } + else if ( readCEN() == -1 ) + { + aEntries.clear(); + throw ZipException("stream data looks to be broken" ); + } + } +} + +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::StaticGetDigestContextForChecksum( 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("Invalid derived key length!" ); + } + + 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("argon2id_ctx failed to derive key"); + } + } + 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("Can not create derived key!" ); + } + + 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("Unknown cipher algorithm is requested!" ); + } + + 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 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 8 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 16 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 24 ) & 0xFF; + + // Then the version + *(pHeader++) = ( n_ConstCurrentVersion >> 0 ) & 0xFF; + *(pHeader++) = ( n_ConstCurrentVersion >> 8 ) & 0xFF; + + // Then the iteration Count + sal_Int32 const nIterationCount = rData->m_oPBKDFIterationCount ? *rData->m_oPBKDFIterationCount : 0; + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 24 ) & 0xFF); + + sal_Int32 const nArgon2t = rData->m_oArgon2Args ? ::std::get<0>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 24) & 0xFF); + + sal_Int32 const nArgon2m = rData->m_oArgon2Args ? ::std::get<1>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 24) & 0xFF); + + sal_Int32 const nArgon2p = rData->m_oArgon2Args ? ::std::get<2>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 24) & 0xFF); + + // FIXME64: need to handle larger sizes + // Then the size: + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 24 ) & 0xFF); + + // Then the encryption algorithm + sal_Int32 nEncAlgID = rData->m_nEncAlg; + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 24 ) & 0xFF); + + // Then the checksum algorithm + sal_Int32 nChecksumAlgID = rData->m_oCheckAlg ? *rData->m_oCheckAlg : 0; + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 24 ) & 0xFF); + + // Then the derived key size + sal_Int32 nDerivedKeySize = rData->m_nDerivedKeySize; + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 24 ) & 0xFF); + + // Then the start key generation algorithm + sal_Int32 nKeyAlgID = rData->m_nStartKeyGenID; + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 24 ) & 0xFF); + + // Then the salt length + *(pHeader++) = static_cast< sal_Int8 >(( nSaltLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSaltLength >> 8 ) & 0xFF); + + // Then the IV length + *(pHeader++) = static_cast< sal_Int8 >(( nIVLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIVLength >> 8 ) & 0xFF); + + // Then the digest length + *(pHeader++) = static_cast< sal_Int8 >(( nDigestLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDigestLength >> 8 ) & 0xFF); + + // Then the mediatype length + *(pHeader++) = static_cast< sal_Int8 >(( nMediaTypeLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nMediaTypeLength >> 8 ) & 0xFF); + + // 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++] & 0xFF; + nVersion |= ( pBuffer[nPos++] & 0xFF ) << 8; + if ( nVersion == n_ConstCurrentVersion ) + { + sal_Int32 nCount = pBuffer[nPos++] & 0xFF; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 8; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 16; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 24; + if (nCount != 0) + { + rData->m_oPBKDFIterationCount.emplace(nCount); + } + else + { + rData->m_oPBKDFIterationCount.reset(); + } + + sal_Int32 nArgon2t = pBuffer[nPos++] & 0xFF; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2m = pBuffer[nPos++] & 0xFF; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2p = pBuffer[nPos++] & 0xFF; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 24; + + if (nArgon2t != 0 && nArgon2m != 0 && nArgon2p != 0) + { + rData->m_oArgon2Args.emplace(nArgon2t, nArgon2m, nArgon2p); + } + else + { + rData->m_oArgon2Args.reset(); + } + + rSize = pBuffer[nPos++] & 0xFF; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 8; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 16; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rEncAlg = pBuffer[nPos++] & 0xFF; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 8; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 16; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rChecksumAlg = pBuffer[nPos++] & 0xFF; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 8; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 16; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rDerivedKeySize = pBuffer[nPos++] & 0xFF; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 8; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 16; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rStartKeyGenID = pBuffer[nPos++] & 0xFF; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 8; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 16; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int16 nSaltLength = pBuffer[nPos++] & 0xFF; + nSaltLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + sal_Int16 nIVLength = ( pBuffer[nPos++] & 0xFF ); + nIVLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + sal_Int16 nDigestLength = pBuffer[nPos++] & 0xFF; + nDigestLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + + sal_Int16 nMediaTypeLength = pBuffer[nPos++] & 0xFF; + nMediaTypeLength |= ( pBuffer[nPos++] & 0xFF ) << 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, + rtl::Reference<comphelper::RefCountedMutex> const& rMutex) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + 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, true); + // 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()) + { + 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; + + Sequence < sal_Int8 > aReadBuffer ( nSize ); + + xStream->readBytes( aReadBuffer, nSize ); + + if (StaticHasValidPassword(m_xContext, aReadBuffer, rData)) + { + return createStreamForZipEntry( + rMutex, rEntry, rData, UNBUFF_STREAM_DATA, true); + } + } + + return {}; +} + +namespace { + +class XBufferedStream : public cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable> +{ + 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 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, + bool bIsEncrypted, + 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, bIsEncrypted, aMediaType, bRecoveryMode); + + if (!bUseBufferedStream) + return xSrcStream; + + uno::Reference<io::XInputStream> xBufStream; +#ifndef EMSCRIPTEN + static const sal_Int32 nThreadingThreshold = 10000; + + // "encrypted-package" is the only data stream, no point in threading it + if (rEntry.sPath != "encrypted-package" && nThreadingThreshold < xSrcStream->available()) + xBufStream = new XBufferedThreadedStream(xSrcStream, xSrcStream->getSize()); + else +#endif + xBufStream = new XBufferedStream(xSrcStream); + + return xBufStream; +} + +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("Encrypted stream without encryption data!" ); + + if (!rData->m_aKey.hasElements()) + throw packages::WrongPasswordException(THROW_WHERE); + + uno::Reference<XSeekable> xSeek(xStream, UNO_QUERY); + if (!xSeek.is()) + throw ZipIOException("The stream must be seekable!"); + + // 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, + bool bIsEncrypted, + 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 (bIsEncrypted && rData.is()) + { + uno::Reference<XInputStream> const xRet(checkValidPassword(rEntry, rData, aMutexHolder)); + if (xRet.is()) + { + return xRet; + } + bNeedRawStream = true; + } + + return createStreamForZipEntry ( aMutexHolder, + rEntry, + rData, + bNeedRawStream ? UNBUFF_STREAM_RAW : UNBUFF_STREAM_DATA, + bIsEncrypted ); +} + +uno::Reference< XInputStream > ZipFile::getDataStream( ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData > &rData, + bool bIsEncrypted, + 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 ( bIsEncrypted ) + { + // in case no digest is provided there is no way + // to detect password correctness + if ( !rData.is() ) + throw ZipException("Encrypted stream without encryption data!" ); + + // 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, 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, + bIsEncrypted ); +} + +uno::Reference< XInputStream > ZipFile::getRawData( ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData >& rData, + bool bIsEncrypted, + 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, bIsEncrypted, bUseBufferedStream ); +} + +uno::Reference< XInputStream > ZipFile::getWrappedRawStream( + ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData >& rData, + 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, true, true, aMediaType ); +} + +void ZipFile::readLOC( ZipEntry &rEntry ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + sal_Int64 nPos = -rEntry.nOffset; + + aGrabber.seek(nPos); + sal_Int32 nTestSig = aGrabber.ReadInt32(); + if (nTestSig != LOCSIG) + throw ZipIOException("Invalid LOC header (bad signature)" ); + + // 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. + + aGrabber.ReadInt16(); //version + aGrabber.ReadInt16(); //flag + aGrabber.ReadInt16(); //how + aGrabber.ReadInt32(); //time + aGrabber.ReadInt32(); //crc + aGrabber.ReadInt32(); //compressed size + aGrabber.ReadInt32(); //size + sal_Int16 nPathLen = aGrabber.ReadInt16(); + sal_Int16 nExtraLen = aGrabber.ReadInt16(); + + if (nPathLen < 0) + { + SAL_WARN("package", "bogus path len of: " << nPathLen); + nPathLen = 0; + } + + rEntry.nOffset = aGrabber.getPosition() + nPathLen + nExtraLen; + + 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 + uno::Sequence<sal_Int8> aNameBuffer(nPathLen); + sal_Int32 nRead = aGrabber.readBytes(aNameBuffer, nPathLen); + if (nRead < aNameBuffer.getLength()) + aNameBuffer.realloc(nRead); + + OUString sLOCPath( reinterpret_cast<const char *>(aNameBuffer.getConstArray()), + aNameBuffer.getLength(), + RTL_TEXTENCODING_UTF8 ); + + if ( rEntry.nPathLen == -1 ) // the file was created + { + rEntry.nPathLen = nPathLen; + rEntry.sPath = sLOCPath; + } + + bBroken = rEntry.nPathLen != nPathLen + || rEntry.sPath != sLOCPath; + } + catch(...) + { + bBroken = true; + } + + if ( bBroken && !bRecoveryMode ) + throw ZipIOException("The stream seems to be broken!" ); +} + +sal_Int32 ZipFile::findEND() +{ + // this method is called in constructor only, no need for mutex + sal_Int32 nPos, nEnd; + Sequence < sal_Int8 > aBuffer; + try + { + sal_Int32 nLength = static_cast <sal_Int32 > (aGrabber.getLength()); + if (nLength < ENDHDR) + return -1; + nPos = nLength - ENDHDR - ZIP_MAXNAMELEN; + nEnd = nPos >= 0 ? nPos : 0 ; + + aGrabber.seek( nEnd ); + + auto nSize = nLength - nEnd; + if (nSize != aGrabber.readBytes(aBuffer, nSize)) + throw ZipException("Zip END signature not found!" ); + + const sal_Int8 *pBuffer = aBuffer.getConstArray(); + + nPos = nSize - ENDHDR; + while ( nPos >= 0 ) + { + if (pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 5 && pBuffer[nPos+3] == 6 ) + return nPos + nEnd; + nPos--; + } + } + catch ( IllegalArgumentException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( NotConnectedException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( BufferSizeExceededException& ) + { + throw ZipException("Zip END signature not found!" ); + } + throw ZipException("Zip END signature not found!" ); +} + +sal_Int32 ZipFile::readCEN() +{ + // this method is called in constructor only, no need for mutex + sal_Int32 nCenPos = -1, nLocPos; + sal_uInt16 nCount; + + try + { + sal_Int32 nEndPos = findEND(); + if (nEndPos == -1) + return -1; + aGrabber.seek(nEndPos + ENDTOT); + sal_uInt16 nTotal = aGrabber.ReadUInt16(); + sal_Int32 nCenLen = aGrabber.ReadInt32(); + sal_Int32 nCenOff = aGrabber.ReadInt32(); + + if ( nTotal * CENHDR > nCenLen ) + throw ZipException("invalid END header (bad entry count)" ); + + if ( nTotal > ZIP_MAXENTRIES ) + throw ZipException("too many entries in ZIP File" ); + + if ( nCenLen < 0 || nCenLen > nEndPos ) + throw ZipException("Invalid END header (bad central directory size)" ); + + nCenPos = nEndPos - nCenLen; + + if ( nCenOff < 0 || nCenOff > nCenPos ) + throw ZipException("Invalid END header (bad central directory size)" ); + + nLocPos = nCenPos - nCenOff; + aGrabber.seek( nCenPos ); + Sequence < sal_Int8 > aCENBuffer ( nCenLen ); + sal_Int64 nRead = aGrabber.readBytes ( aCENBuffer, nCenLen ); + if ( static_cast < sal_Int64 > ( nCenLen ) != nRead ) + throw ZipException ("Error reading CEN into memory buffer!" ); + + MemoryByteGrabber aMemGrabber(aCENBuffer); + + ZipEntry aEntry; + sal_Int16 nCommentLen; + + aEntries.reserve(nTotal); + for (nCount = 0 ; nCount < nTotal; nCount++) + { + sal_Int32 nTestSig = aMemGrabber.ReadInt32(); + if ( nTestSig != CENSIG ) + throw ZipException("Invalid CEN header (bad signature)" ); + + aMemGrabber.skipBytes ( 2 ); + aEntry.nVersion = aMemGrabber.ReadInt16(); + aEntry.nFlag = aMemGrabber.ReadInt16(); + + if ( ( aEntry.nFlag & 1 ) == 1 ) + throw ZipException("Invalid CEN header (encrypted entry)" ); + + aEntry.nMethod = aMemGrabber.ReadInt16(); + + if ( aEntry.nMethod != STORED && aEntry.nMethod != DEFLATED) + throw ZipException("Invalid CEN header (bad compression method)" ); + + 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 ( 8 ); + sal_uInt64 nOffset = aMemGrabber.ReadUInt32(); + + if ( aEntry.nPathLen < 0 ) + throw ZipException("unexpected name length" ); + + if ( nCommentLen < 0 ) + throw ZipException("unexpected comment length" ); + + if ( aEntry.nExtraLen < 0 ) + throw ZipException("unexpected extra header info length" ); + + if (aEntry.nPathLen > aMemGrabber.remainingSize()) + throw ZipException("name too long"); + + // read always in UTF8, some tools seem not to set UTF8 bit + aEntry.sPath = OUString( reinterpret_cast<char const *>(aMemGrabber.getCurrentPos()), + aEntry.nPathLen, + RTL_TEXTENCODING_UTF8 ); + + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aEntry.sPath, true ) ) + throw ZipException("Zip entry has an invalid name." ); + + aMemGrabber.skipBytes(aEntry.nPathLen); + + if (aEntry.nExtraLen>0) + { + readExtraFields(aMemGrabber, aEntry.nExtraLen, nSize, nCompressedSize, &nOffset); + } + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + aEntry.nOffset = nOffset; + + if (o3tl::checked_add<sal_Int64>(aEntry.nOffset, nLocPos, aEntry.nOffset)) + throw ZipException("Integer-overflow"); + if (o3tl::checked_multiply<sal_Int64>(aEntry.nOffset, -1, aEntry.nOffset)) + throw ZipException("Integer-overflow"); + + aMemGrabber.skipBytes(nCommentLen); + aEntries[aEntry.sPath] = aEntry; + } + + if (nCount != nTotal) + throw ZipException("Count != Total" ); + } + catch ( IllegalArgumentException & ) + { + // seek can throw this... + nCenPos = -1; // make sure we return -1 to indicate an error + } + return nCenPos; +} + +void ZipFile::readExtraFields(MemoryByteGrabber& aMemGrabber, sal_Int16 nExtraLen, + sal_uInt64& nSize, sal_uInt64& nCompressedSize, sal_uInt64* nOffset) +{ + 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 && nOffset) + { + *nOffset = aMemGrabber.ReadUInt64(); + nReadSize = 24; + // 4 byte should be "Disk Start Number" but we not need it + } + } + if (dataSize > nReadSize) + aMemGrabber.skipBytes(dataSize - nReadSize); + } + else + { + aMemGrabber.skipBytes(dataSize); + } + nExtraLen -= dataSize + 4; + } +} + +void ZipFile::recover() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + sal_Int64 nLength; + Sequence < sal_Int8 > aBuffer; + + try + { + nLength = aGrabber.getLength(); + if (nLength < ENDHDR) + return; + + aGrabber.seek( 0 ); + + const sal_Int64 nToRead = 32000; + for( sal_Int64 nGenPos = 0; aGrabber.readBytes( aBuffer, nToRead ) && aBuffer.getLength() > 16; ) + { + const sal_Int8 *pBuffer = aBuffer.getConstArray(); + sal_Int32 nBufSize = aBuffer.getLength(); + + 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 ( nPos < nBufSize - 30 && pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 3 && pBuffer[nPos+3] == 4 ) + { + //PK34: Local file header + ZipEntry aEntry; + Sequence<sal_Int8> aTmpBuffer(&(pBuffer[nPos+4]), 26); + MemoryByteGrabber aMemGrabber(aTmpBuffer); + + aEntry.nVersion = aMemGrabber.ReadInt16(); + aEntry.nFlag = aMemGrabber.ReadInt16(); + + if ( ( aEntry.nFlag & 1 ) != 1 ) + { + aEntry.nMethod = aMemGrabber.ReadInt16(); + + if ( aEntry.nMethod == STORED || aEntry.nMethod == DEFLATED ) + { + 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(); + + sal_Int32 nDescrLength = + ( aEntry.nMethod == DEFLATED && ( aEntry.nFlag & 8 ) ) ? 16 : 0; + + sal_Int64 nBlockHeaderLength = aEntry.nPathLen + aEntry.nExtraLen + 30 + nDescrLength; + if ( aEntry.nPathLen >= 0 && aEntry.nExtraLen >= 0 + && ( nGenPos + nPos + nBlockHeaderLength ) <= nLength ) + { + // read always in UTF8, some tools seem not to set UTF8 bit + if( nPos + 30 + aEntry.nPathLen <= nBufSize ) + aEntry.sPath = OUString ( reinterpret_cast<char const *>(&pBuffer[nPos + 30]), + aEntry.nPathLen, + RTL_TEXTENCODING_UTF8 ); + else + { + Sequence < sal_Int8 > aFileName; + aGrabber.seek( nGenPos + nPos + 30 ); + aGrabber.readBytes( aFileName, aEntry.nPathLen ); + aEntry.sPath = OUString ( reinterpret_cast<const char *>(aFileName.getConstArray()), + aFileName.getLength(), + RTL_TEXTENCODING_UTF8 ); + aEntry.nPathLen = static_cast< sal_Int16 >(aFileName.getLength()); + } + + // read 64bit header + if (aEntry.nExtraLen > 0) + { + Sequence<sal_Int8> aExtraBuffer; + if (nPos + 30 + aEntry.nPathLen + aEntry.nExtraLen <= nBufSize) + { + aExtraBuffer = Sequence<sal_Int8>( + &(pBuffer[nPos + 30 + aEntry.nPathLen]), + aEntry.nExtraLen); + } + else + { + aGrabber.seek(nGenPos + nPos + 30 + aEntry.nExtraLen); + aGrabber.readBytes(aExtraBuffer, aEntry.nExtraLen); + } + MemoryByteGrabber aMemGrabberExtra(aExtraBuffer); + if (aEntry.nExtraLen > 0) + { + readExtraFields(aMemGrabberExtra, aEntry.nExtraLen, nSize, + nCompressedSize, nullptr); + } + } + + sal_Int64 nDataSize = ( aEntry.nMethod == DEFLATED ) ? nCompressedSize : nSize; + sal_Int64 nBlockLength = nDataSize + nBlockHeaderLength; + + if (( nGenPos + nPos + nBlockLength ) <= nLength ) + { + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + + aEntry.nOffset = nGenPos + nPos + 30 + aEntry.nPathLen + aEntry.nExtraLen; + + if ( ( aEntry.nSize || aEntry.nCompressedSize ) && !checkSizeAndCRC( aEntry ) ) + { + aEntry.nCrc = 0; + aEntry.nCompressedSize = 0; + aEntry.nSize = 0; + } + + aEntries.emplace( aEntry.sPath, aEntry ); + } + } + } + } + + nPos += 4; + } + else if (pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 7 && pBuffer[nPos+3] == 8 ) + { + //PK78: Data descriptor + sal_Int64 nCompressedSize, nSize; + Sequence<sal_Int8> aTmpBuffer(&(pBuffer[nPos + 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 ((pBuffer[nPos + 16] == 'P' && pBuffer[nPos + 17] == 'K' + && pBuffer[nPos + 19] == pBuffer[nPos + 18] + 1 + && (pBuffer[nPos + 18] == 3 || pBuffer[nPos + 18] == 1)) + || !(pBuffer[nPos + 24] == 'P' && pBuffer[nPos + 25] == 'K' + && pBuffer[nPos + 27] == pBuffer[nPos + 26] + 1 + && (pBuffer[nPos + 26] == 3 || pBuffer[nPos + 26] == 1))) + { + nCompressedSize = aMemGrabber.ReadUInt32(); + nSize = aMemGrabber.ReadUInt32(); + } + else + { + nCompressedSize = aMemGrabber.ReadUInt64(); + nSize = aMemGrabber.ReadUInt64(); + } + + for( auto& rEntry : aEntries ) + { + // this is a broken package, accept this block not only for DEFLATED streams + if( rEntry.second.nFlag & 8 ) + { + sal_Int64 nStreamOffset = nGenPos + nPos - 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; + } + } +#if 0 +// for now ignore clearly broken streams + else if( !rEntry.second.nCompressedSize ) + { + rEntry.second.nCrc = nCRC32; + sal_Int32 nRealStreamSize = nGenPos + nPos - rEntry.second.nOffset; + rEntry.second.nCompressedSize = nRealStreamSize; + rEntry.second.nSize = nSize; + } +#endif + } + } + + nPos += 4; + } + else + nPos++; + } + + nGenPos += nPos; + aGrabber.seek( nGenPos ); + } + } + catch ( IllegalArgumentException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( NotConnectedException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( BufferSizeExceededException& ) + { + throw ZipException("Zip END signature not found!" ); + } +} + +bool ZipFile::checkSizeAndCRC( const ZipEntry& aEntry ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + 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 ); +} + +sal_Int32 ZipFile::getCRC( sal_Int64 nOffset, sal_Int64 nSize ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + Sequence < sal_Int8 > aBuffer; + CRC32 aCRC; + sal_Int64 nBlockSize = ::std::min(nSize, static_cast< sal_Int64 >(32000)); + + aGrabber.seek( nOffset ); + for (sal_Int64 ind = 0; + aGrabber.readBytes( aBuffer, nBlockSize ) && ind * nBlockSize < nSize; + ++ind) + { + sal_Int64 nLen = ::std::min(nBlockSize, nSize - ind * nBlockSize); + aCRC.updateSegment(aBuffer, static_cast<sal_Int32>(nLen)); + } + + return aCRC.getValue(); +} + +void ZipFile::getSizeAndCRC( sal_Int64 nOffset, sal_Int64 nCompressedSize, sal_Int64 *nSize, sal_Int32 *nCRC ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + Sequence < sal_Int8 > aBuffer; + CRC32 aCRC; + sal_Int64 nRealSize = 0; + Inflater aInflaterLocal( true ); + sal_Int32 nBlockSize = static_cast< sal_Int32 > (::std::min( nCompressedSize, static_cast< sal_Int64 >( 32000 ) ) ); + + aGrabber.seek( nOffset ); + for ( sal_Int64 ind = 0; + !aInflaterLocal.finished() && aGrabber.readBytes( aBuffer, nBlockSize ) && ind * nBlockSize < nCompressedSize; + ind++ ) + { + Sequence < sal_Int8 > aData( nBlockSize ); + sal_Int32 nLastInflated = 0; + sal_Int64 nInBlock = 0; + + aInflaterLocal.setInput( aBuffer ); + do + { + nLastInflated = aInflaterLocal.doInflateSegment( aData, 0, nBlockSize ); + aCRC.updateSegment( aData, nLastInflated ); + nInBlock += nLastInflated; + } while( !aInflater.finished() && nLastInflated ); + + nRealSize += nInBlock; + } + + *nSize = nRealSize; + *nCRC = aCRC.getValue(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |