| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the Collabora Office 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; |
| |
| /** 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::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(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 ) & 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, |
| 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(u""_ustr, 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(); |
| |
| 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(); |
| } |
| } |
| 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(); |
| } |
| |
| 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(); |
| } |
| 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(); |
| |
| 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 & ~0x806U) != (nLocFlag & ~0x806U)) |
| { |
| 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 & 0x08) != 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 == 0x08074b50) // 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 != 0xFFFF) |
| { // only single disk is supported! |
| throw ZipException(u"invalid end (disk)"_ustr ); |
| } |
| sal_uInt16 const nEndDirDisk = aGrabber.ReadUInt16(); |
| if (nEndDirDisk != 0 && nEndDisk != 0xFFFF) |
| { |
| 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 |
| || nEnd64DirSize < 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 & 0xff00) == 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 == 0x7075 && 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: */ |