diff options
Diffstat (limited to '')
19 files changed, 3946 insertions, 0 deletions
diff --git a/package/source/zipapi/ByteChucker.cxx b/package/source/zipapi/ByteChucker.cxx new file mode 100644 index 000000000..6c5994aa6 --- /dev/null +++ b/package/source/zipapi/ByteChucker.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <ByteChucker.hxx> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XOutputStream.hpp> + +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; + +ByteChucker::ByteChucker(Reference<XOutputStream> const & xOstream) +: xStream(xOstream) +, xSeek (xOstream, UNO_QUERY ) +, a2Sequence ( 2 ) +, a4Sequence ( 4 ) +, p2Sequence ( a2Sequence.getArray() ) +, p4Sequence ( a4Sequence.getArray() ) +{ +} + +ByteChucker::~ByteChucker() +{ +} + +void ByteChucker::WriteBytes( const Sequence< sal_Int8 >& aData ) +{ + xStream->writeBytes(aData); +} + +sal_Int64 ByteChucker::GetPosition( ) +{ + return xSeek->getPosition(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ByteGrabber.cxx b/package/source/zipapi/ByteGrabber.cxx new file mode 100644 index 000000000..b58a7087f --- /dev/null +++ b/package/source/zipapi/ByteGrabber.cxx @@ -0,0 +1,125 @@ +/* -*- 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 <ByteGrabber.hxx> +#include <sal/log.hxx> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +/** ByteGrabber implements the >> operators on an XOutputStream. This is + * potentially quite slow and may need to be optimised + */ + +ByteGrabber::ByteGrabber(uno::Reference < io::XInputStream > const & xIstream) +: xStream(xIstream) +, xSeek (xIstream, uno::UNO_QUERY ) +, aSequence ( 4 ) +{ + pSequence = aSequence.getArray(); +} + +ByteGrabber::~ByteGrabber() +{ +} + +void ByteGrabber::setInputStream (const uno::Reference < io::XInputStream >& xNewStream) +{ + std::scoped_lock aGuard( m_aMutex ); + xStream = xNewStream; + xSeek.set(xNewStream, uno::UNO_QUERY); +} + +// XInputStream chained +sal_Int32 ByteGrabber::readBytes( uno::Sequence< sal_Int8 >& aData, + sal_Int32 nBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + return xStream->readBytes(aData, nBytesToRead ); +} + +// XSeekable chained... +void ByteGrabber::seek( sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + sal_Int64 nLen = xSeek->getLength(); + if ( location < 0 || location > nLen ) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + xSeek->seek( location ); +} + +sal_Int64 ByteGrabber::getPosition( ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + return xSeek->getPosition(); +} + +sal_Int64 ByteGrabber::getLength( ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + return xSeek->getLength(); +} + +sal_uInt16 ByteGrabber::ReadUInt16() +{ + std::scoped_lock aGuard( m_aMutex ); + + if (xStream->readBytes(aSequence, 2) != 2) + return 0; + + pSequence = aSequence.getConstArray(); + return static_cast <sal_uInt16> + ( (pSequence[0] & 0xFF) + | (pSequence[1] & 0xFF) << 8); +} + +sal_uInt32 ByteGrabber::ReadUInt32() +{ + std::scoped_lock aGuard( m_aMutex ); + + if (xStream->readBytes(aSequence, 4) != 4) + return 0; + + pSequence = aSequence.getConstArray(); + return static_cast < sal_uInt32 > + ( (pSequence[0] & 0xFF) + | ( pSequence[1] & 0xFF ) << 8 + | ( pSequence[2] & 0xFF ) << 16 + | ( pSequence[3] & 0xFF ) << 24 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/CRC32.cxx b/package/source/zipapi/CRC32.cxx new file mode 100644 index 000000000..bd0682248 --- /dev/null +++ b/package/source/zipapi/CRC32.cxx @@ -0,0 +1,72 @@ +/* -*- 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 <CRC32.hxx> +#include <PackageConstants.hxx> +#include <rtl/crc.h> +#include <com/sun/star/io/XInputStream.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::io; + +/** A class to compute the CRC32 value of a data stream + */ + +CRC32::CRC32() +: nCRC(0) +{ +} +void CRC32::reset() +{ + nCRC=0; +} +sal_Int32 CRC32::getValue() const +{ + return nCRC; +} +/** Update CRC32 with specified sequence of bytes + */ +void CRC32::updateSegment(const Sequence< sal_Int8 > &b, sal_Int32 len) +{ + nCRC = rtl_crc32(nCRC, b.getConstArray(), len ); +} +/** Update CRC32 with specified sequence of bytes + */ +void CRC32::update(const Sequence< sal_Int8 > &b) +{ + nCRC = rtl_crc32(nCRC, b.getConstArray(),b.getLength()); +} + +sal_Int64 CRC32::updateStream( Reference < XInputStream > const & xStream ) +{ + sal_Int32 nLength; + sal_Int64 nTotal = 0; + Sequence < sal_Int8 > aSeq ( n_ConstBufferSize ); + do + { + nLength = xStream->readBytes ( aSeq, n_ConstBufferSize ); + updateSegment ( aSeq, nLength ); + nTotal += nLength; + } + while ( nLength == n_ConstBufferSize ); + + return nTotal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/Deflater.cxx b/package/source/zipapi/Deflater.cxx new file mode 100644 index 000000000..5c0fd7012 --- /dev/null +++ b/package/source/zipapi/Deflater.cxx @@ -0,0 +1,148 @@ +/* -*- 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 <package/Deflater.hxx> +#include <zlib.h> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <osl/diagnose.h> +#include <string.h> + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star; +using namespace ZipUtils; + +/** Provides general purpose compression using the ZLIB compression + * library. + */ + +Deflater::~Deflater() +{ + end(); +} +void Deflater::init (sal_Int32 nLevelArg, bool bNowrap) +{ + pStream.reset(new z_stream); + /* Memset it to 0...sets zalloc/zfree/opaque to NULL */ + memset (pStream.get(), 0, sizeof(*pStream)); + + switch (deflateInit2(pStream.get(), nLevelArg, Z_DEFLATED, bNowrap? -MAX_WBITS : MAX_WBITS, + DEF_MEM_LEVEL, DEFAULT_STRATEGY)) + { + case Z_OK: + break; + case Z_MEM_ERROR: + pStream.reset(); + break; + case Z_STREAM_ERROR: + pStream.reset(); + break; + default: + break; + } +} + +Deflater::Deflater(sal_Int32 nSetLevel, bool bNowrap) +: bFinish(false) +, bFinished(false) +, nOffset(0) +, nLength(0) +{ + init(nSetLevel, bNowrap); +} + +sal_Int32 Deflater::doDeflateBytes (uno::Sequence < sal_Int8 > &rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength) +{ + sal_Int32 nResult; + pStream->next_in = reinterpret_cast<const unsigned char*>( sInBuffer.getConstArray() + nOffset ); + pStream->next_out = reinterpret_cast<unsigned char*>(rBuffer.getArray())+nNewOffset; + pStream->avail_in = nLength; + pStream->avail_out = nNewLength; + +#if !defined Z_PREFIX + nResult = deflate(pStream.get(), bFinish ? Z_FINISH : Z_NO_FLUSH); +#else + nResult = z_deflate(pStream.get(), bFinish ? Z_FINISH : Z_NO_FLUSH); +#endif + switch (nResult) + { + case Z_STREAM_END: + bFinished = true; + [[fallthrough]]; + case Z_OK: + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return nNewLength - pStream->avail_out; + default: + return 0; + } +} + +void Deflater::setInputSegment( const uno::Sequence< sal_Int8 >& rBuffer ) +{ + sInBuffer = rBuffer; + nOffset = 0; + nLength = rBuffer.getLength(); +} + +bool Deflater::needsInput() const +{ + return nLength <=0; +} +void Deflater::finish( ) +{ + bFinish = true; +} +sal_Int32 Deflater::doDeflateSegment( uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewLength ) +{ + OSL_ASSERT( !(nNewLength < 0 || nNewLength > rBuffer.getLength())); + return doDeflateBytes(rBuffer, /*nNewOffset*/0, nNewLength); +} +sal_Int64 Deflater::getTotalIn() const +{ + return pStream->total_in; // FIXME64: zlib doesn't look 64bit clean here +} +sal_Int64 Deflater::getTotalOut() const +{ + return pStream->total_out; // FIXME64: zlib doesn't look 64bit clean here +} +void Deflater::reset( ) +{ +#if !defined Z_PREFIX + deflateReset(pStream.get()); +#else + z_deflateReset(pStream.get()); +#endif + bFinish = false; + bFinished = false; + nOffset = nLength = 0; +} +void Deflater::end( ) +{ + if (pStream) + { +#if !defined Z_PREFIX + deflateEnd(pStream.get()); +#else + z_deflateEnd(pStream.get()); +#endif + pStream.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/Inflater.cxx b/package/source/zipapi/Inflater.cxx new file mode 100644 index 000000000..d03fed8c0 --- /dev/null +++ b/package/source/zipapi/Inflater.cxx @@ -0,0 +1,137 @@ +/* -*- 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 <package/Inflater.hxx> +#include <zlib.h> +#include <string.h> + +using namespace com::sun::star::uno; +using namespace ZipUtils; + +/** Provides general purpose decompression using the ZLIB library */ + +Inflater::Inflater(bool bNoWrap) +: bFinished(false), + bNeedDict(false), + nOffset(0), + nLength(0), + nLastInflateError(0) +{ + pStream.reset(new z_stream); + /* memset to 0 to set zalloc/opaque etc */ + memset (pStream.get(), 0, sizeof(*pStream)); + sal_Int32 nRes; + nRes = inflateInit2(pStream.get(), bNoWrap ? -MAX_WBITS : MAX_WBITS); + switch (nRes) + { + case Z_OK: + break; + case Z_MEM_ERROR: + pStream.reset(); + break; + case Z_STREAM_ERROR: + pStream.reset(); + break; + default: + break; + } +} + +Inflater::~Inflater() +{ + end(); +} + +void Inflater::setInput( const Sequence< sal_Int8 >& rBuffer ) +{ + sInBuffer = rBuffer; + nOffset = 0; + nLength = rBuffer.getLength(); +} + + +sal_Int32 Inflater::doInflateSegment( Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength ) +{ + if (nNewOffset < 0 || nNewLength < 0 || nNewOffset + nNewLength > rBuffer.getLength()) + { + // do error handling + } + return doInflateBytes(rBuffer, nNewOffset, nNewLength); +} + +void Inflater::end( ) +{ + if (pStream) + { +#if !defined Z_PREFIX + inflateEnd(pStream.get()); +#else + z_inflateEnd(pStream.get()); +#endif + pStream.reset(); + } +} + +sal_Int32 Inflater::doInflateBytes (Sequence < sal_Int8 > &rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength) +{ + if ( !pStream ) + { + nLastInflateError = Z_STREAM_ERROR; + return 0; + } + + nLastInflateError = 0; + + pStream->next_in = reinterpret_cast<const unsigned char*>( sInBuffer.getConstArray() + nOffset ); + pStream->avail_in = nLength; + pStream->next_out = reinterpret_cast < unsigned char* > ( rBuffer.getArray() + nNewOffset ); + pStream->avail_out = nNewLength; + +#if !defined Z_PREFIX + sal_Int32 nResult = ::inflate(pStream.get(), Z_PARTIAL_FLUSH); +#else + sal_Int32 nResult = ::z_inflate(pStream.get(), Z_PARTIAL_FLUSH); +#endif + + switch (nResult) + { + case Z_STREAM_END: + bFinished = true; + [[fallthrough]]; + case Z_OK: + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return nNewLength - pStream->avail_out; + + case Z_NEED_DICT: + bNeedDict = true; + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return 0; + + default: + // it is no error, if there is no input or no output + if ( nLength && nNewLength ) + nLastInflateError = nResult; + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/MemoryByteGrabber.hxx b/package/source/zipapi/MemoryByteGrabber.hxx new file mode 100644 index 000000000..8dcf7f067 --- /dev/null +++ b/package/source/zipapi/MemoryByteGrabber.hxx @@ -0,0 +1,88 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_MEMORYBYTEGRABBER_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_MEMORYBYTEGRABBER_HXX + +#include <com/sun/star/uno/Sequence.h> + +class MemoryByteGrabber final +{ + const sal_Int8 *mpBuffer; + sal_Int32 mnCurrent, mnEnd; +public: + MemoryByteGrabber ( const css::uno::Sequence < sal_Int8 > & rBuffer ) + : mpBuffer ( rBuffer.getConstArray() ) + , mnCurrent ( 0 ) + , mnEnd ( rBuffer.getLength() ) + { + } + MemoryByteGrabber(css::uno::Sequence<sal_Int8> &&) = delete; + + const sal_Int8 * getCurrentPos () const { return mpBuffer + mnCurrent; } + + sal_Int32 remainingSize() const { return mnEnd - mnCurrent; } + + // XInputStream chained + + /// @throws css::io::NotConnectedException + /// @throws css::io::BufferSizeExceededException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void skipBytes( sal_Int32 nBytesToSkip ) + { + mnCurrent += nBytesToSkip; + } + + // XSeekable chained... + sal_Int16 ReadInt16() + { + if (mnCurrent + 2 > mnEnd ) + return 0; + sal_Int16 nInt16 = mpBuffer[mnCurrent++] & 0xFF; + nInt16 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 8; + return nInt16; + } + sal_Int32 ReadInt32() + { + if (mnCurrent + 4 > mnEnd ) + return 0; + + sal_Int32 nInt32 = mpBuffer[mnCurrent++] & 0xFF; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 8; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 16; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 24; + return nInt32; + } + + sal_uInt32 ReadUInt32() + { + if (mnCurrent + 4 > mnEnd ) + return 0; + + sal_uInt32 nInt32 = mpBuffer [mnCurrent++] & 0xFF; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 8; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 16; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 24; + return nInt32; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ThreadedDeflater.cxx b/package/source/zipapi/ThreadedDeflater.cxx new file mode 100644 index 000000000..f574105ad --- /dev/null +++ b/package/source/zipapi/ThreadedDeflater.cxx @@ -0,0 +1,220 @@ +/* -*- 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 <ThreadedDeflater.hxx> +#include <zlib.h> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <sal/log.hxx> + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star; + +namespace ZipUtils +{ +const sal_Int64 MaxBlockSize = 128 * 1024; + +// Parallel ZLIB compression using threads. The class internally splits the data into +// blocks and spawns ThreadPool tasks to process them independently. This is achieved +// in a similar way how pigz works, see comments from Mark Adler at +// https://stackoverflow.com/questions/30294766/how-to-use-multiple-threads-for-zlib-compression +// and +// https://stackoverflow.com/questions/30794053/how-to-use-multiple-threads-for-zlib-compression-same-input-source + +// Everything here should be either read-only, or writing to distinct data, or atomic. + +class ThreadedDeflater::Task : public comphelper::ThreadTask +{ + z_stream stream; + ThreadedDeflater* deflater; + int sequence; + int blockSize; + bool firstTask : 1; + bool lastTask : 1; + +public: + Task(ThreadedDeflater* deflater_, int sequence_, int blockSize_, bool firstTask_, + bool lastTask_) + : comphelper::ThreadTask(deflater_->threadTaskTag) + , stream() + , deflater(deflater_) + , sequence(sequence_) + , blockSize(blockSize_) + , firstTask(firstTask_) + , lastTask(lastTask_) + { + } + +private: + virtual void doWork() override; +}; + +ThreadedDeflater::ThreadedDeflater(sal_Int32 nSetLevel) + : threadTaskTag(comphelper::ThreadPool::createThreadTaskTag()) + , totalIn(0) + , totalOut(0) + , zlibLevel(nSetLevel) +{ +} + +ThreadedDeflater::~ThreadedDeflater() COVERITY_NOEXCEPT_FALSE { clear(); } + +void ThreadedDeflater::deflateWrite( + const css::uno::Reference<css::io::XInputStream>& xInStream, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessInputFunc, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessOutputFunc) +{ + sal_Int64 nThreadCount = comphelper::ThreadPool::getSharedOptimalPool().getWorkerCount(); + sal_Int64 batchSize = MaxBlockSize * nThreadCount; + inBuffer.realloc(batchSize); + prevDataBlock.realloc(MaxBlockSize); + outBuffers.resize(nThreadCount); + maProcessOutputFunc = aProcessOutputFunc; + bool firstTask = true; + + while (xInStream->available() > 0) + { + sal_Int64 inputBytes = xInStream->readBytes(inBuffer, batchSize); + aProcessInputFunc(inBuffer, inputBytes); + totalIn += inputBytes; + int sequence = 0; + bool lastBatch = xInStream->available() <= 0; + sal_Int64 bytesPending = inputBytes; + while (bytesPending > 0) + { + sal_Int64 taskSize = std::min(MaxBlockSize, bytesPending); + bytesPending -= taskSize; + bool lastTask = lastBatch && !bytesPending; + comphelper::ThreadPool::getSharedOptimalPool().pushTask( + std::make_unique<Task>(this, sequence++, taskSize, firstTask, lastTask)); + + if (firstTask) + firstTask = false; + } + + assert(bytesPending == 0); + + comphelper::ThreadPool::getSharedOptimalPool().waitUntilDone(threadTaskTag); + + if (!lastBatch) + { + assert(inputBytes == batchSize); + std::copy_n(std::cbegin(inBuffer) + (batchSize - MaxBlockSize), MaxBlockSize, + prevDataBlock.getArray()); + } + + processDeflatedBuffers(); + } +} + +void ThreadedDeflater::processDeflatedBuffers() +{ + sal_Int64 batchOutputSize = 0; + for (const auto& buffer : outBuffers) + batchOutputSize += buffer.size(); + + css::uno::Sequence<sal_Int8> outBuffer(batchOutputSize); + + auto pos = outBuffer.getArray(); + for (auto& buffer : outBuffers) + { + pos = std::copy(buffer.begin(), buffer.end(), pos); + buffer.clear(); + } + + maProcessOutputFunc(outBuffer, batchOutputSize); + totalOut += batchOutputSize; +} + +void ThreadedDeflater::clear() +{ + inBuffer = uno::Sequence<sal_Int8>(); + outBuffers.clear(); +} + +#if defined Z_PREFIX +#define deflateInit2 z_deflateInit2 +#define deflateBound z_deflateBound +#define deflateSetDictionary z_deflateSetDictionary +#define deflate z_deflate +#define deflateEnd z_deflateEnd +#endif + +void ThreadedDeflater::Task::doWork() +{ + stream.zalloc = nullptr; + stream.zfree = nullptr; + stream.opaque = nullptr; + // -MAX_WBITS means 32k window size and raw stream + if (deflateInit2(&stream, deflater->zlibLevel, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY) + != Z_OK) + { + SAL_WARN("package.threadeddeflate", "deflateInit2() failed"); + abort(); + } + // Find out size for our output buffer to be large enough for deflate() needing to be called just once. + sal_Int64 outputMaxSize = deflateBound(&stream, blockSize); + // add extra size for Z_SYNC_FLUSH + outputMaxSize += 20; + deflater->outBuffers[sequence].resize(outputMaxSize); + sal_Int64 myInBufferStart = sequence * MaxBlockSize; + // zlib doesn't handle const properly + unsigned char* inBufferPtr = reinterpret_cast<unsigned char*>( + const_cast<signed char*>(deflater->inBuffer.getConstArray())); + if (!firstTask) + { + // the window size is 32k, so set last 32k of previous data as the dictionary + assert(MAX_WBITS == 15); + assert(MaxBlockSize >= 32768); + if (sequence > 0) + { + deflateSetDictionary(&stream, inBufferPtr + myInBufferStart - 32768, 32768); + } + else + { + unsigned char* prevBufferPtr = reinterpret_cast<unsigned char*>( + const_cast<signed char*>(deflater->prevDataBlock.getConstArray())); + deflateSetDictionary(&stream, prevBufferPtr + MaxBlockSize - 32768, 32768); + } + } + stream.next_in = inBufferPtr + myInBufferStart; + stream.avail_in = blockSize; + stream.next_out = reinterpret_cast<unsigned char*>(deflater->outBuffers[sequence].data()); + stream.avail_out = outputMaxSize; + + // The trick is in using Z_SYNC_FLUSH instead of Z_NO_FLUSH. It will align the data at a byte boundary, + // and since we use a raw stream, the data blocks then can be simply concatenated. + int res = deflate(&stream, lastTask ? Z_FINISH : Z_SYNC_FLUSH); + assert(stream.avail_in == 0); // Check that everything has been deflated. + if (lastTask ? res == Z_STREAM_END : res == Z_OK) + { // ok + sal_Int64 outSize = outputMaxSize - stream.avail_out; + deflater->outBuffers[sequence].resize(outSize); + } + else + { + SAL_WARN("package.threadeddeflate", "deflate() failed"); + abort(); + } + deflateEnd(&stream); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XBufferedThreadedStream.cxx b/package/source/zipapi/XBufferedThreadedStream.cxx new file mode 100644 index 000000000..d3bf995d9 --- /dev/null +++ b/package/source/zipapi/XBufferedThreadedStream.cxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "XBufferedThreadedStream.hxx" + +using namespace css::uno; + +namespace { + +class UnzippingThread: public salhelper::Thread +{ + XBufferedThreadedStream &mxStream; +public: + explicit UnzippingThread(XBufferedThreadedStream &xStream): Thread("Unzipping"), mxStream(xStream) {} +private: + virtual void execute() override + { + try + { + mxStream.produce(); + } + catch (...) + { + mxStream.saveException(std::current_exception()); + } + + mxStream.setTerminateThread(); + } +}; + +} + +XBufferedThreadedStream::XBufferedThreadedStream( + const Reference<XInputStream>& xSrcStream, + sal_Int64 nStreamSize) +: mxSrcStream( xSrcStream ) +, mnPos(0) +, mnStreamSize( nStreamSize ) +, mnOffset( 0 ) +, mxUnzippingThread( new UnzippingThread(*this) ) +, mbTerminateThread( false ) +{ + mxUnzippingThread->launch(); +} + +XBufferedThreadedStream::~XBufferedThreadedStream() +{ + setTerminateThread(); + mxUnzippingThread->join(); +} + +/** + * Reads from UnbufferedStream in a separate thread and stores the buffer blocks + * in maPendingBuffers queue for further use. + */ +void XBufferedThreadedStream::produce() +{ + Buffer pProducedBuffer; + sal_Int64 nTotalBytesRead(0); + std::unique_lock<std::mutex> aGuard( maBufferProtector ); + do + { + if( !maUsedBuffers.empty() ) + { + pProducedBuffer = maUsedBuffers.front(); + maUsedBuffers.pop(); + } + + aGuard.unlock(); + nTotalBytesRead += mxSrcStream->readBytes( pProducedBuffer, nBufferSize ); + + aGuard.lock(); + maPendingBuffers.push( pProducedBuffer ); + maBufferConsumeResume.notify_one(); + + if (!mbTerminateThread) + maBufferProduceResume.wait( aGuard, [&]{return canProduce(); } ); + + } while( !mbTerminateThread && nTotalBytesRead < mnStreamSize ); +} + +/** + * Fetches next available block from maPendingBuffers for use in Reading thread. + */ +const Buffer& XBufferedThreadedStream::getNextBlock() +{ + std::unique_lock<std::mutex> aGuard( maBufferProtector ); + const sal_Int32 nBufSize = maInUseBuffer.getLength(); + if( nBufSize <= 0 || mnOffset >= nBufSize ) + { + if( mnOffset >= nBufSize ) + maUsedBuffers.push( maInUseBuffer ); + + maBufferConsumeResume.wait( aGuard, [&]{return canConsume(); } ); + + if( maPendingBuffers.empty() ) + { + maInUseBuffer = Buffer(); + if (maSavedException) + std::rethrow_exception(maSavedException); + } + else + { + maInUseBuffer = maPendingBuffers.front(); + maPendingBuffers.pop(); + mnOffset = 0; + + if( maPendingBuffers.size() <= nBufferLowWater ) + maBufferProduceResume.notify_one(); + } + } + + return maInUseBuffer; +} + +void XBufferedThreadedStream::setTerminateThread() +{ + std::scoped_lock<std::mutex> aGuard( maBufferProtector ); + mbTerminateThread = true; + maBufferProduceResume.notify_one(); + maBufferConsumeResume.notify_one(); +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::readBytes( Sequence< sal_Int8 >& rData, sal_Int32 nBytesToRead ) +{ + if( !hasBytes() ) + return 0; + + const sal_Int32 nAvailableSize = static_cast< sal_Int32 > ( std::min< sal_Int64 >( nBytesToRead, remainingSize() ) ); + rData.realloc( nAvailableSize ); + auto pData = rData.getArray(); + sal_Int32 i = 0, nPendingBytes = nAvailableSize; + + while( nPendingBytes ) + { + const Buffer &pBuffer = getNextBlock(); + if( !pBuffer.hasElements() ) + { + rData.realloc( nAvailableSize - nPendingBytes ); + return nAvailableSize - nPendingBytes; + } + const sal_Int32 limit = std::min<sal_Int32>( nPendingBytes, pBuffer.getLength() - mnOffset ); + + memcpy( &pData[i], &pBuffer[mnOffset], limit ); + + nPendingBytes -= limit; + mnOffset += limit; + mnPos += limit; + i += limit; + } + + return nAvailableSize; +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes( aData, nMaxBytesToRead ); +} +void SAL_CALL XBufferedThreadedStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if( nBytesToSkip ) + { + Sequence < sal_Int8 > aSequence( nBytesToSkip ); + readBytes( aSequence, nBytesToSkip ); + } +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::available() +{ + if( !hasBytes() ) + return 0; + + return static_cast< sal_Int32 > ( std::min< sal_Int64 >( SAL_MAX_INT32, remainingSize() ) ); +} + +void SAL_CALL XBufferedThreadedStream::closeInput() +{ + setTerminateThread(); + mxUnzippingThread->join(); + mxSrcStream->closeInput(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XBufferedThreadedStream.hxx b/package/source/zipapi/XBufferedThreadedStream.hxx new file mode 100644 index 000000000..ad5d3b0ce --- /dev/null +++ b/package/source/zipapi/XBufferedThreadedStream.hxx @@ -0,0 +1,83 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_XBUFFEREDTHREADEDSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_XBUFFEREDTHREADEDSTREAM_HXX + +#include <com/sun/star/io/XInputStream.hpp> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> + +#include <queue> +#include <mutex> +#include <condition_variable> + +typedef css::uno::Sequence< sal_Int8 > Buffer; + +class XBufferedThreadedStream : public cppu::WeakImplHelper< css::io::XInputStream > +{ +private: + const css::uno::Reference<XInputStream> mxSrcStream; + sal_Int64 mnPos; /// position in stream + sal_Int64 mnStreamSize; /// available size of stream + + Buffer maInUseBuffer; /// Buffer block in use + int mnOffset; /// position in maInUseBuffer + std::queue < Buffer > maPendingBuffers; /// Buffers that are available for use + std::queue < Buffer > maUsedBuffers; + + rtl::Reference< salhelper::Thread > mxUnzippingThread; + std::mutex maBufferProtector; /// mutex protecting Buffer queues. + std::condition_variable maBufferConsumeResume; + std::condition_variable maBufferProduceResume; + bool mbTerminateThread; /// indicates the failure of one of the threads + + std::exception_ptr maSavedException; /// exception caught during unzipping is saved to be thrown during reading + + static const size_t nBufferLowWater = 2; + static const size_t nBufferHighWater = 4; + static const size_t nBufferSize = 32 * 1024; + + const Buffer& getNextBlock(); + sal_Int64 remainingSize() const { return mnStreamSize - mnPos; } + bool hasBytes() const { return mnPos < mnStreamSize; } + + bool canProduce() const + { + return( mbTerminateThread || maPendingBuffers.size() < nBufferHighWater ); + } + + bool canConsume() const + { + return( mbTerminateThread || !maPendingBuffers.empty() ); + } + +public: + XBufferedThreadedStream( + const css::uno::Reference<XInputStream>& xSrcStream, + sal_Int64 nStreamSize /* cf. sal_Int32 available(); */ ); + + virtual ~XBufferedThreadedStream() override; + + void produce(); + void setTerminateThread(); + void saveException(const std::exception_ptr& exception) { maSavedException = exception; } + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XUnbufferedStream.cxx b/package/source/zipapi/XUnbufferedStream.cxx new file mode 100644 index 000000000..56f864a2d --- /dev/null +++ b/package/source/zipapi/XUnbufferedStream.cxx @@ -0,0 +1,323 @@ +/* -*- 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/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> + +#include "XUnbufferedStream.hxx" +#include <EncryptionData.hxx> +#include <ZipFile.hxx> +#include <EncryptedDataHeader.hxx> +#include <algorithm> +#include <string.h> + +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using com::sun::star::packages::zip::ZipIOException; + +XUnbufferedStream::XUnbufferedStream( + const uno::Reference< uno::XComponentContext >& xContext, + const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder, + ZipEntry const & rEntry, + Reference < XInputStream > const & xNewZipStream, + const ::rtl::Reference< EncryptionData >& rData, + sal_Int8 nStreamMode, + bool bIsEncrypted, + const OUString& aMediaType, + bool bRecoveryMode ) +: maMutexHolder( aMutexHolder ) +, mxZipStream ( xNewZipStream ) +, mxZipSeek ( xNewZipStream, UNO_QUERY ) +, maEntry ( rEntry ) +, mnBlockSize( 1 ) +, maInflater ( true ) +, mbRawStream ( nStreamMode == UNBUFF_STREAM_RAW || nStreamMode == UNBUFF_STREAM_WRAPPEDRAW ) +, mbWrappedRaw ( nStreamMode == UNBUFF_STREAM_WRAPPEDRAW ) +, mnHeaderToRead ( 0 ) +, mnZipCurrent ( 0 ) +, mnZipEnd ( 0 ) +, mnZipSize ( 0 ) +, mnMyCurrent ( 0 ) +, mbCheckCRC(!bRecoveryMode) +{ + mnZipCurrent = maEntry.nOffset; + if ( mbRawStream ) + { + mnZipSize = maEntry.nMethod == DEFLATED ? maEntry.nCompressedSize : maEntry.nSize; + mnZipEnd = maEntry.nOffset + mnZipSize; + } + else + { + mnZipSize = maEntry.nSize; + mnZipEnd = maEntry.nMethod == DEFLATED ? maEntry.nOffset + maEntry.nCompressedSize : maEntry.nOffset + maEntry.nSize; + } + + if (mnZipSize < 0) + throw ZipIOException("The stream seems to be broken!"); + + bool bHaveEncryptData = rData.is() && rData->m_aInitVector.hasElements() && + ((rData->m_aSalt.hasElements() && rData->m_nIterationCount != 0) + || + rData->m_aKey.hasElements()); + bool bMustDecrypt = nStreamMode == UNBUFF_STREAM_DATA && bHaveEncryptData && bIsEncrypted; + + if ( bMustDecrypt ) + { + m_xCipherContext = ZipFile::StaticGetCipher( xContext, rData, false ); + mnBlockSize = ( rData->m_nEncAlg == xml::crypto::CipherID::AES_CBC_W3C_PADDING ? 16 : 1 ); + } + + if ( !(bHaveEncryptData && mbWrappedRaw && bIsEncrypted) ) + return; + + // if we have the data needed to decrypt it, but didn't want it decrypted (or + // we couldn't decrypt it due to wrong password), then we prepend this + // data to the stream + + // Make a buffer big enough to hold both the header and the data itself + maHeader.realloc ( n_ConstHeaderSize + + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + + rData->m_aDigest.getLength() + + aMediaType.getLength() * sizeof( sal_Unicode ) ); + sal_Int8 * pHeader = maHeader.getArray(); + ZipFile::StaticFillHeader( rData, rEntry.nSize, aMediaType, pHeader ); + mnHeaderToRead = static_cast < sal_Int16 > ( maHeader.getLength() ); + mnZipSize += mnHeaderToRead; +} + +// allows to read package raw stream +XUnbufferedStream::XUnbufferedStream( + const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder, + const Reference < XInputStream >& xRawStream, + const ::rtl::Reference< EncryptionData >& rData ) +: maMutexHolder( aMutexHolder ) +, mxZipStream ( xRawStream ) +, mxZipSeek ( xRawStream, UNO_QUERY ) +, mnBlockSize( 1 ) +, maInflater ( true ) +, mbRawStream ( false ) +, mbWrappedRaw ( false ) +, mnHeaderToRead ( 0 ) +, mnZipCurrent ( 0 ) +, mnZipEnd ( 0 ) +, mnZipSize ( 0 ) +, mnMyCurrent ( 0 ) +, mbCheckCRC( false ) +{ + // for this scenario maEntry is not set !!! + OSL_ENSURE( mxZipSeek.is(), "The stream must be seekable!" ); + + // skip raw header, it must be already parsed to rData + mnZipCurrent = n_ConstHeaderSize + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + rData->m_aDigest.getLength(); + + try { + if ( mxZipSeek.is() ) + mnZipSize = mxZipSeek->getLength(); + } catch( const Exception& ) + { + // in case of problem the size will stay set to 0 + TOOLS_WARN_EXCEPTION("package", "ignoring"); + } + + mnZipEnd = mnZipCurrent + mnZipSize; + + // the raw data will not be decrypted, no need for the cipher + // m_xCipherContext = ZipFile::StaticGetCipher( xContext, rData, false ); +} + +XUnbufferedStream::~XUnbufferedStream() +{ +} + +sal_Int32 SAL_CALL XUnbufferedStream::readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + ::osl::MutexGuard aGuard( maMutexHolder->GetMutex() ); + + sal_Int32 nRequestedBytes = nBytesToRead; + OSL_ENSURE( !mnHeaderToRead || mbWrappedRaw, "Only encrypted raw stream can be provided with header!" ); + if ( mnMyCurrent + nRequestedBytes > mnZipSize + maHeader.getLength() ) + nRequestedBytes = static_cast < sal_Int32 > ( mnZipSize + maHeader.getLength() - mnMyCurrent ); + + sal_Int32 nTotal = 0; + aData.realloc ( nRequestedBytes ); + if ( nRequestedBytes ) + { + sal_Int32 nRead = 0; + sal_Int32 nLastRead = 0; + if ( mbRawStream ) + { + sal_Int64 nDiff = mnZipEnd - mnZipCurrent; + + if ( mbWrappedRaw && mnHeaderToRead ) + { + sal_Int16 nHeadRead = static_cast< sal_Int16 >(( nRequestedBytes > mnHeaderToRead ? + mnHeaderToRead : nRequestedBytes )); + memcpy ( aData.getArray(), maHeader.getConstArray() + maHeader.getLength() - mnHeaderToRead, nHeadRead ); + mnHeaderToRead = mnHeaderToRead - nHeadRead; + + if ( nHeadRead < nRequestedBytes ) + { + sal_Int32 nToRead = nRequestedBytes - nHeadRead; + nToRead = ( nDiff < nToRead ) ? sal::static_int_cast< sal_Int32 >( nDiff ) : nToRead; + + Sequence< sal_Int8 > aPureData( nToRead ); + mxZipSeek->seek ( mnZipCurrent ); + nRead = mxZipStream->readBytes ( aPureData, nToRead ); + mnZipCurrent += nRead; + + aPureData.realloc( nRead ); + if ( mbCheckCRC ) + maCRC.update( aPureData ); + + aData.realloc( nHeadRead + nRead ); + + const sal_Int8* pPureBuffer = aPureData.getConstArray(); + sal_Int8* pBuffer = aData.getArray(); + for ( sal_Int32 nInd = 0; nInd < nRead; nInd++ ) + pBuffer[ nHeadRead + nInd ] = pPureBuffer[ nInd ]; + } + + nRead += nHeadRead; + } + else + { + mxZipSeek->seek ( mnZipCurrent ); + + nRead = mxZipStream->readBytes ( + aData, + std::min<sal_Int64>(nDiff, nRequestedBytes) ); + + mnZipCurrent += nRead; + + aData.realloc( nRead ); + if ( mbWrappedRaw && mbCheckCRC ) + maCRC.update( aData ); + } + } + else + { + for (;;) + { + nLastRead = maInflater.doInflateSegment( aData, nRead, aData.getLength() - nRead ); + if ( 0 != nLastRead && ( nRead + nLastRead == nRequestedBytes || mnZipCurrent >= mnZipEnd ) ) + break; + nRead += nLastRead; + if ( nRead > nRequestedBytes ) + throw RuntimeException( + "Should not be possible to read more than requested!" ); + + if ( maInflater.finished() || maInflater.getLastInflateError() ) + throw ZipIOException("The stream seems to be broken!" ); + + if ( maInflater.needsDictionary() ) + throw ZipIOException("Dictionaries are not supported!" ); + + sal_Int32 nDiff = static_cast< sal_Int32 >( mnZipEnd - mnZipCurrent ); + if ( nDiff <= 0 ) + { + throw ZipIOException("The stream seems to be broken!" ); + } + + mxZipSeek->seek ( mnZipCurrent ); + + sal_Int32 nToRead = std::max( nRequestedBytes, static_cast< sal_Int32 >( 8192 ) ); + if ( mnBlockSize > 1 ) + nToRead = nToRead + mnBlockSize - nToRead % mnBlockSize; + nToRead = std::min( nDiff, nToRead ); + + sal_Int32 nZipRead = mxZipStream->readBytes( maCompBuffer, nToRead ); + if ( nZipRead < nToRead ) + throw ZipIOException("No expected data!" ); + + mnZipCurrent += nZipRead; + // maCompBuffer now has the data, check if we need to decrypt + // before passing to the Inflater + if ( m_xCipherContext.is() ) + { + if ( mbCheckCRC ) + maCRC.update( maCompBuffer ); + + maCompBuffer = m_xCipherContext->convertWithCipherContext( maCompBuffer ); + if ( mnZipCurrent == mnZipEnd ) + { + uno::Sequence< sal_Int8 > aSuffix = m_xCipherContext->finalizeCipherContextAndDispose(); + if ( aSuffix.hasElements() ) + { + sal_Int32 nOldLen = maCompBuffer.getLength(); + maCompBuffer.realloc( nOldLen + aSuffix.getLength() ); + memcpy( maCompBuffer.getArray() + nOldLen, aSuffix.getConstArray(), aSuffix.getLength() ); + } + } + } + maInflater.setInput ( maCompBuffer ); + + } + } + + mnMyCurrent += nRead + nLastRead; + nTotal = nRead + nLastRead; + if ( nTotal < nRequestedBytes) + aData.realloc ( nTotal ); + + if ( mbCheckCRC && ( !mbRawStream || mbWrappedRaw ) ) + { + if ( !m_xCipherContext.is() && !mbWrappedRaw ) + maCRC.update( aData ); + + if ( mnZipSize + maHeader.getLength() == mnMyCurrent && maCRC.getValue() != maEntry.nCrc ) + throw ZipIOException("The stream seems to be broken!" ); + } + } + + return nTotal; +} + +sal_Int32 SAL_CALL XUnbufferedStream::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes ( aData, nMaxBytesToRead ); +} +void SAL_CALL XUnbufferedStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if ( nBytesToSkip ) + { + Sequence < sal_Int8 > aSequence ( nBytesToSkip ); + readBytes ( aSequence, nBytesToSkip ); + } +} + +sal_Int32 SAL_CALL XUnbufferedStream::available( ) +{ + //available size must include the prepended header in case of wrapped raw stream + return static_cast< sal_Int32 > ( std::min< sal_Int64 >( SAL_MAX_INT32, (mnZipSize + mnHeaderToRead - mnMyCurrent) ) ); +} + +void SAL_CALL XUnbufferedStream::closeInput( ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XUnbufferedStream.hxx b/package/source/zipapi/XUnbufferedStream.hxx new file mode 100644 index 000000000..277f356ee --- /dev/null +++ b/package/source/zipapi/XUnbufferedStream.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_XUNBUFFEREDSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_XUNBUFFEREDSTREAM_HXX + +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/xml/crypto/XCipherContext.hpp> + +#include <comphelper/refcountedmutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <package/Inflater.hxx> +#include <ZipEntry.hxx> +#include <CRC32.hxx> + +namespace com::sun::star::uno { + class XComponentContext; +} + +#define UNBUFF_STREAM_DATA 0 +#define UNBUFF_STREAM_RAW 1 +#define UNBUFF_STREAM_WRAPPEDRAW 2 + +class EncryptionData; +class XUnbufferedStream final : public cppu::WeakImplHelper +< + css::io::XInputStream +> +{ + rtl::Reference<comphelper::RefCountedMutex> maMutexHolder; + + css::uno::Reference < css::io::XInputStream > mxZipStream; + css::uno::Reference < css::io::XSeekable > mxZipSeek; + css::uno::Sequence < sal_Int8 > maCompBuffer, maHeader; + ZipEntry maEntry; + sal_Int32 mnBlockSize; + css::uno::Reference< css::xml::crypto::XCipherContext > m_xCipherContext; + ZipUtils::Inflater maInflater; + bool mbRawStream, mbWrappedRaw; + sal_Int16 mnHeaderToRead; + sal_Int64 mnZipCurrent, mnZipEnd, mnZipSize, mnMyCurrent; + CRC32 maCRC; + bool mbCheckCRC; + +public: + XUnbufferedStream( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + ZipEntry const & rEntry, + css::uno::Reference < css::io::XInputStream > const & xNewZipStream, + const ::rtl::Reference< EncryptionData >& rData, + sal_Int8 nStreamMode, + bool bIsEncrypted, + const OUString& aMediaType, + bool bRecoveryMode ); + + // allows to read package raw stream + XUnbufferedStream( + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + const css::uno::Reference < css::io::XInputStream >& xRawStream, + const ::rtl::Reference< EncryptionData >& rData ); + + sal_Int64 getSize() const { return mnZipSize; } + + virtual ~XUnbufferedStream() override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipEnumeration.cxx b/package/source/zipapi/ZipEnumeration.cxx new file mode 100644 index 000000000..0c036882f --- /dev/null +++ b/package/source/zipapi/ZipEnumeration.cxx @@ -0,0 +1,39 @@ +/* -*- 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 <ZipEnumeration.hxx> + +/** Provides an Enumeration over the contents of a Zip file */ + +ZipEnumeration::ZipEnumeration(EntryHash& rNewEntryHash) + : rEntryHash(rNewEntryHash) + , aIterator(rEntryHash.begin()) +{ +} +bool ZipEnumeration::hasMoreElements() { return (aIterator != rEntryHash.end()); } + +const ZipEntry* ZipEnumeration::nextElement() +{ + if (aIterator != rEntryHash.end()) + return &((*aIterator++).second); + else + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx new file mode 100644 index 000000000..79dc5bb68 --- /dev/null +++ b/package/source/zipapi/ZipFile.cxx @@ -0,0 +1,1263 @@ +/* -*- 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/lang/XUnoTunnel.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 <rtl/digest.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <iterator> +#include <vector> + +#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( const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + uno::Reference < XInputStream > const &xInput, + const uno::Reference < XComponentContext > & rxContext, + bool bInitialise ) +: m_aMutexHolder( aMutexHolder ) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext ( rxContext ) +, bRecoveryMode( false ) +{ + if (bInitialise && readCEN() == -1 ) + { + aEntries.clear(); + throw ZipException( "stream data looks to be broken" ); + } +} + +ZipFile::ZipFile( const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder, + uno::Reference < XInputStream > const &xInput, + const uno::Reference < XComponentContext > & rxContext, + bool bInitialise, bool bForceRecovery) +: m_aMutexHolder( aMutexHolder ) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext ( rxContext ) +, 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 ) +{ + uno::Reference< xml::crypto::XDigestContext > xDigestContext; + if ( xEncryptionData->m_nCheckAlg == 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_nCheckAlg, uno::Sequence< beans::NamedValue >() ), uno::UNO_SET_THROW ); + } + else if ( xEncryptionData->m_nCheckAlg == 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_nIterationCount && + 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 ( 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_nIterationCount ) ) + { + throw ZipIOException("Can not create derived key!" ); + } + + if ( xEncryptionData->m_nEncAlg == xml::crypto::CipherID::AES_CBC_W3C_PADDING ) + { + 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 nIterationCount = rData->m_nIterationCount; + *(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); + + // 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_nCheckAlg; + *(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; + rData->m_nIterationCount = nCount; + + 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; +} + +uno::Reference< XInputStream > ZipFile::StaticGetDataFromRawStream( const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder, + 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 + OSL_ENSURE( rData->m_aDigest.hasElements(), "Can't detect password correctness without digest!" ); + 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( aMutexHolder, xStream, rData ); +} + +#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 ) +{ + 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; +} + +bool ZipFile::hasValidPassword ( ZipEntry const & rEntry, const ::rtl::Reference< EncryptionData >& rData ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + bool bRet = false; + 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 ); + + bRet = StaticHasValidPassword( m_xContext, aReadBuffer, rData ); + } + + return bRet; +} + +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(); + uno::Reference< css::lang::XUnoTunnel > xInputTunnel( xSrcStream, uno::UNO_QUERY ); + comphelper::ByteReader* pByteReader = nullptr; + if (xInputTunnel) + pByteReader = reinterpret_cast< comphelper::ByteReader* >( xInputTunnel->getSomething( comphelper::ByteReader::getUnoTunnelId() ) ); + + if (pByteReader) + { + maBytes.resize(nRemaining); + + sal_Int8* pData = maBytes.data(); + while (nRemaining > 0) + { + sal_Int32 nRead = pByteReader->readSomeBytes(pData, nRemaining); + nRemaining -= nRead; + pData += nRead; + } + } + else + { + const sal_Int32 nBufSize = 8192; + + sal_Int32 nRead = 0; + maBytes.reserve(nRemaining); + uno::Sequence<sal_Int8> aBuf(nBufSize); + + auto readAndCopy = [&]( sal_Int32 nReadSize ) -> sal_Int32 + { + sal_Int32 nBytes = xSrcStream->readBytes(aBuf, nReadSize); + const sal_Int8* p = aBuf.getConstArray(); + const sal_Int8* pEnd = p + nBytes; + maBytes.insert( maBytes.end(), p, pEnd ); + return nBytes; + }; + + while (nRemaining > nBufSize) + { + const auto nBytes = readAndCopy(nBufSize); + if (!nBytes) + break; + nRead += nBytes; + nRemaining -= nBytes; + } + + if (nRemaining) + nRead += readAndCopy(nRemaining); + maBytes.resize(nRead); + } + } + + 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; + + if( xSrcStream->available() > nThreadingThreshold ) + xBufStream = new XBufferedThreadedStream(xSrcStream, xSrcStream->getSize()); + else +#endif + xBufStream = new XBufferedStream(xSrcStream); + + return xBufStream; +} + +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 we have a digest, then this file is an encrypted one and we should + // check if we can decrypt it or not + if ( bIsEncrypted && rData.is() && rData->m_aDigest.hasElements() ) + bNeedRawStream = !hasValidPassword ( rEntry, rData ); + + 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 + OSL_ENSURE( rData->m_aDigest.hasElements(), "Can't detect password correctness without digest!" ); + if ( rData->m_aDigest.hasElements() && !hasValidPassword ( rEntry, rData ) ) + throw packages::WrongPasswordException(THROW_WHERE ); + } + 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(); + rEntry.nOffset = aGrabber.getPosition() + nPathLen + nExtraLen; + + // FIXME64: need to read 64bit LOC + + bool bBroken = false; + + try + { + sal_Int16 nPathLenToRead = nPathLen; + const sal_Int64 nBytesAvailable = aGrabber.getLength() - aGrabber.getPosition(); + if (nPathLenToRead > nBytesAvailable) + nPathLenToRead = nBytesAvailable; + else if (nPathLenToRead < 0) + nPathLenToRead = 0; + + // read always in UTF8, some tools seem not to set UTF8 bit + uno::Sequence<sal_Int8> aNameBuffer(nPathLenToRead); + sal_Int32 nRead = aGrabber.readBytes(aNameBuffer, nPathLenToRead); + 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_uInt32 nCompressedSize = aMemGrabber.ReadUInt32(); + sal_uInt32 nSize = aMemGrabber.ReadUInt32(); + aEntry.nPathLen = aMemGrabber.ReadInt16(); + aEntry.nExtraLen = aMemGrabber.ReadInt16(); + nCommentLen = aMemGrabber.ReadInt16(); + aMemGrabber.skipBytes ( 8 ); + sal_uInt32 nOffset = aMemGrabber.ReadUInt32(); + + // FIXME64: need to read the 64bit header instead + if ( nSize == 0xffffffff || + nOffset == 0xffffffff || + nCompressedSize == 0xffffffff ) { + throw ZipException("PK64 zip file entry" ); + } + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + aEntry.nOffset = nOffset; + + aEntry.nOffset += nLocPos; + aEntry.nOffset *= -1; + + 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 + aEntry.nExtraLen + 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::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 ) + { + 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_uInt32 nCompressedSize = aMemGrabber.ReadUInt32(); + sal_uInt32 nSize = aMemGrabber.ReadUInt32(); + aEntry.nPathLen = aMemGrabber.ReadInt16(); + aEntry.nExtraLen = aMemGrabber.ReadInt16(); + + // FIXME64: need to read the 64bit header instead + if ( nSize == 0xffffffff || + nCompressedSize == 0xffffffff ) { + throw ZipException("PK64 zip file entry" ); + } + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + + sal_Int32 nDescrLength = + ( aEntry.nMethod == DEFLATED && ( aEntry.nFlag & 8 ) ) ? 16 : 0; + + sal_Int64 nDataSize = ( aEntry.nMethod == DEFLATED ) ? aEntry.nCompressedSize : aEntry.nSize; + sal_Int64 nBlockLength = nDataSize + aEntry.nPathLen + aEntry.nExtraLen + 30 + nDescrLength; + if ( aEntry.nPathLen >= 0 && aEntry.nExtraLen >= 0 + && ( nGenPos + nPos + nBlockLength ) <= 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()); + } + + 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 ) + { + sal_Int64 nCompressedSize, nSize; + Sequence<sal_Int8> aTmpBuffer(&(pBuffer[nPos+4]), 12); + MemoryByteGrabber aMemGrabber(aTmpBuffer); + sal_Int32 nCRC32 = aMemGrabber.ReadInt32(); + sal_uInt32 nCompressedSize32 = aMemGrabber.ReadUInt32(); + sal_uInt32 nSize32 = aMemGrabber.ReadUInt32(); + + // FIXME64: work to be done here ... + nCompressedSize = nCompressedSize32; + nSize = nSize32; + + 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 ); + + 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: */ diff --git a/package/source/zipapi/ZipOutputEntry.cxx b/package/source/zipapi/ZipOutputEntry.cxx new file mode 100644 index 000000000..897ea6212 --- /dev/null +++ b/package/source/zipapi/ZipOutputEntry.cxx @@ -0,0 +1,400 @@ +/* -*- 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 <ZipOutputEntry.hxx> + +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> + +#include <osl/diagnose.h> + +#include <PackageConstants.hxx> +#include <ThreadedDeflater.hxx> +#include <ZipEntry.hxx> +#include <ZipFile.hxx> +#include <ZipPackageStream.hxx> + +#include <algorithm> + +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::packages::zip::ZipConstants; + +/** This class is used to deflate Zip entries + */ +ZipOutputEntryBase::ZipOutputEntryBase( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt, + bool checkStream) +: m_xContext(rxContext) +, m_xOutStream(rxOutput) +, m_pCurrentEntry(&rEntry) +, m_nDigested(0) +, m_pCurrentStream(pStream) +, m_bEncryptCurrentEntry(bEncrypt) +{ + assert(m_pCurrentEntry->nMethod == DEFLATED && "Use ZipPackageStream::rawWrite() for STORED entries"); + (void)checkStream; + assert(!checkStream || m_xOutStream.is()); + if (m_bEncryptCurrentEntry) + { + m_xCipherContext = ZipFile::StaticGetCipher( m_xContext, pStream->GetEncryptionData(), true ); + m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xContext, pStream->GetEncryptionData() ); + } +} + +void ZipOutputEntryBase::closeEntry() +{ + finishDeflater(); + + if ((m_pCurrentEntry->nFlag & 8) == 0) + { + if (m_pCurrentEntry->nSize != getDeflaterTotalIn()) + { + OSL_FAIL("Invalid entry size"); + } + if (m_pCurrentEntry->nCompressedSize != getDeflaterTotalOut()) + { + // Different compression strategies make the merit of this + // test somewhat dubious + m_pCurrentEntry->nCompressedSize = getDeflaterTotalOut(); + } + if (m_pCurrentEntry->nCrc != m_aCRC.getValue()) + { + OSL_FAIL("Invalid entry CRC-32"); + } + } + else + { + if ( !m_bEncryptCurrentEntry ) + { + m_pCurrentEntry->nSize = getDeflaterTotalIn(); + m_pCurrentEntry->nCompressedSize = getDeflaterTotalOut(); + } + m_pCurrentEntry->nCrc = m_aCRC.getValue(); + } + deflaterReset(); + m_aCRC.reset(); + + if (!m_bEncryptCurrentEntry) + return; + + m_xCipherContext.clear(); + + uno::Sequence< sal_Int8 > aDigestSeq; + if ( m_xDigestContext.is() ) + { + aDigestSeq = m_xDigestContext->finalizeDigestAndDispose(); + m_xDigestContext.clear(); + } + + if ( m_pCurrentStream ) + m_pCurrentStream->setDigest( aDigestSeq ); +} + +void ZipOutputEntryBase::processDeflated( const uno::Sequence< sal_Int8 >& deflateBuffer, sal_Int32 nLength ) +{ + if ( nLength > 0 ) + { + uno::Sequence< sal_Int8 > aTmpBuffer( deflateBuffer.getConstArray(), nLength ); + if ( m_bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() ) + { + // Need to update our digest before encryption... + sal_Int32 nDiff = n_ConstDigestLength - m_nDigested; + if ( nDiff ) + { + sal_Int32 nEat = ::std::min( nLength, nDiff ); + uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat ); + m_xDigestContext->updateDigest( aTmpSeq ); + m_nDigested = m_nDigested + static_cast< sal_Int16 >( nEat ); + } + + // FIXME64: uno::Sequence not 64bit safe. + uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer ); + + m_xOutStream->writeBytes( aEncryptionBuffer ); + + // the sizes as well as checksum for encrypted streams is calculated here + m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); + m_pCurrentEntry->nSize = m_pCurrentEntry->nCompressedSize; + m_aCRC.update( aEncryptionBuffer ); + } + else + { + m_xOutStream->writeBytes ( aTmpBuffer ); + } + } + + if ( !(isDeflaterFinished() && m_bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is()) ) + return; + + // FIXME64: sequence not 64bit safe. + uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose(); + if ( aEncryptionBuffer.hasElements() ) + { + m_xOutStream->writeBytes( aEncryptionBuffer ); + + // the sizes as well as checksum for encrypted streams are calculated here + m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); + m_pCurrentEntry->nSize = m_pCurrentEntry->nCompressedSize; + m_aCRC.update( aEncryptionBuffer ); + } +} + +void ZipOutputEntryBase::processInput( const uno::Sequence< sal_Int8 >& rBuffer ) +{ + if (!m_bEncryptCurrentEntry) + m_aCRC.updateSegment(rBuffer, rBuffer.getLength()); +} + +ZipOutputEntry::ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt, + bool checkStream) +: ZipOutputEntryBase(rxOutput, rxContext, rEntry, pStream, bEncrypt, checkStream) +, m_aDeflateBuffer(n_ConstBufferSize) +, m_aDeflater(DEFAULT_COMPRESSION, true) +{ +} + +ZipOutputEntry::ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntry( rxOutput, rxContext, rEntry, pStream, bEncrypt, true) +{ +} + +void ZipOutputEntry::write( const Sequence< sal_Int8 >& rBuffer ) +{ + if (!m_aDeflater.finished()) + { + m_aDeflater.setInputSegment(rBuffer); + while (!m_aDeflater.needsInput()) + doDeflate(); + processInput(rBuffer); + } +} + +void ZipOutputEntry::doDeflate() +{ + sal_Int32 nLength = m_aDeflater.doDeflateSegment(m_aDeflateBuffer, m_aDeflateBuffer.getLength()); + processDeflated( m_aDeflateBuffer, nLength ); +} + +void ZipOutputEntry::finishDeflater() +{ + m_aDeflater.finish(); + while (!m_aDeflater.finished()) + doDeflate(); +} + +sal_Int64 ZipOutputEntry::getDeflaterTotalIn() const +{ + return m_aDeflater.getTotalIn(); +} + +sal_Int64 ZipOutputEntry::getDeflaterTotalOut() const +{ + return m_aDeflater.getTotalOut(); +} + +void ZipOutputEntry::deflaterReset() +{ + m_aDeflater.reset(); +} + +bool ZipOutputEntry::isDeflaterFinished() const +{ + return m_aDeflater.finished(); +} + + +ZipOutputEntryInThread::ZipOutputEntryInThread( + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntry( uno::Reference< css::io::XOutputStream >(), rxContext, rEntry, pStream, bEncrypt, false ) +, m_bFinished(false) +{ +} + +void ZipOutputEntryInThread::createBufferFile() +{ + assert(!m_xOutStream && !m_xTempFile && + "should only be called in the threaded mode where there is no existing stream yet"); + m_xTempFile.set( + io::TempFile::create(m_xContext), + uno::UNO_SET_THROW ); + m_xOutStream = m_xTempFile->getOutputStream(); +} + +void ZipOutputEntryInThread::closeBufferFile() +{ + m_xOutStream->closeOutput(); + m_xOutStream.clear(); +} + +void ZipOutputEntryInThread::deleteBufferFile() +{ + assert(!m_xOutStream.is() && m_xTempFile); + m_xTempFile.clear(); +} + +uno::Reference< io::XInputStream > ZipOutputEntryInThread::getData() const +{ + return m_xTempFile->getInputStream(); +} + +class ZipOutputEntryInThread::Task : public comphelper::ThreadTask +{ + ZipOutputEntryInThread *mpEntry; + uno::Reference< io::XInputStream > mxInStream; + +public: + Task( const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, ZipOutputEntryInThread *pEntry, + const uno::Reference< io::XInputStream >& xInStream ) + : comphelper::ThreadTask(pTag) + , mpEntry(pEntry) + , mxInStream(xInStream) + {} + +private: + virtual void doWork() override + { + try + { + mpEntry->createBufferFile(); + mpEntry->writeStream(mxInStream); + mxInStream.clear(); + mpEntry->closeBufferFile(); + mpEntry->setFinished(); + } + catch (...) + { + mpEntry->setParallelDeflateException(std::current_exception()); + try + { + if (mpEntry->m_xOutStream.is()) + mpEntry->closeBufferFile(); + if (mpEntry->m_xTempFile) + mpEntry->deleteBufferFile(); + } + catch (uno::Exception const&) + { + } + mpEntry->setFinished(); + } + } +}; + +std::unique_ptr<comphelper::ThreadTask> ZipOutputEntryInThread::createTask( + const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + const uno::Reference< io::XInputStream >& xInStream ) +{ + return std::make_unique<Task>(pTag, this, xInStream); +} + +void ZipOutputEntry::writeStream(const uno::Reference< io::XInputStream >& xInStream) +{ + sal_Int32 nLength = 0; + uno::Sequence< sal_Int8 > aSeq(n_ConstBufferSize); + do + { + nLength = xInStream->readBytes(aSeq, n_ConstBufferSize); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + write(aSeq); + } + while (nLength == n_ConstBufferSize); + closeEntry(); +} + + +ZipOutputEntryParallel::ZipOutputEntryParallel( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntryBase(rxOutput, rxContext, rEntry, pStream, bEncrypt, true) +, totalIn(0) +, totalOut(0) +, finished(false) +{ +} + +void ZipOutputEntryParallel::writeStream(const uno::Reference< io::XInputStream >& xInStream) +{ + ZipUtils::ThreadedDeflater deflater( DEFAULT_COMPRESSION ); + deflater.deflateWrite(xInStream, + [this](const uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nLen) { + if (!m_bEncryptCurrentEntry) + m_aCRC.updateSegment(rBuffer, nLen); + }, + [this](const uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nLen) { + processDeflated(rBuffer, nLen); + } + ); + finished = true; + processDeflated( uno::Sequence< sal_Int8 >(), 0 ); // finish encrypting, etc. + totalIn = deflater.getTotalIn(); + totalOut = deflater.getTotalOut(); + closeEntry(); +} + +void ZipOutputEntryParallel::finishDeflater() +{ + // ThreadedDeflater is called synchronously in one call, so nothing to do here. +} + +sal_Int64 ZipOutputEntryParallel::getDeflaterTotalIn() const +{ + return totalIn; +} + +sal_Int64 ZipOutputEntryParallel::getDeflaterTotalOut() const +{ + return totalOut; +} + +void ZipOutputEntryParallel::deflaterReset() +{ + totalIn = 0; + totalOut = 0; + finished = false; +} + +bool ZipOutputEntryParallel::isDeflaterFinished() const +{ + return finished; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipOutputStream.cxx b/package/source/zipapi/ZipOutputStream.cxx new file mode 100644 index 000000000..33321627b --- /dev/null +++ b/package/source/zipapi/ZipOutputStream.cxx @@ -0,0 +1,365 @@ +/* -*- 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 <ZipOutputStream.hxx> + +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <comphelper/storagehelper.hxx> + +#include <osl/time.h> +#include <osl/thread.hxx> + +#include <PackageConstants.hxx> +#include <ZipEntry.hxx> +#include <ZipOutputEntry.hxx> +#include <ZipPackageStream.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::packages::zip::ZipConstants; + +/** This class is used to write Zip files + */ +ZipOutputStream::ZipOutputStream( const uno::Reference < io::XOutputStream > &xOStream ) +: m_xStream(xOStream) +, mpThreadTaskTag( comphelper::ThreadPool::createThreadTaskTag() ) +, m_aChucker(xOStream) +, m_pCurrentEntry(nullptr) +{ +} + +ZipOutputStream::~ZipOutputStream() +{ +} + +void ZipOutputStream::setEntry( ZipEntry *pEntry ) +{ + if (pEntry->nTime == -1) + pEntry->nTime = getCurrentDosTime(); + if (pEntry->nMethod == -1) + pEntry->nMethod = DEFLATED; + pEntry->nVersion = 20; + pEntry->nFlag = 1 << 11; + if (pEntry->nSize == -1 || pEntry->nCompressedSize == -1 || + pEntry->nCrc == -1) + { + pEntry->nSize = pEntry->nCompressedSize = 0; + pEntry->nFlag |= 8; + } +} + +void ZipOutputStream::addDeflatingThreadTask( ZipOutputEntryInThread *pEntry, std::unique_ptr<comphelper::ThreadTask> pTask ) +{ + comphelper::ThreadPool::getSharedOptimalPool().pushTask(std::move(pTask)); + m_aEntries.push_back(pEntry); +} + +void ZipOutputStream::rawWrite( const Sequence< sal_Int8 >& rBuffer ) +{ + m_aChucker.WriteBytes( rBuffer ); +} + +void ZipOutputStream::rawCloseEntry( bool bEncrypt ) +{ + assert(m_pCurrentEntry && "Forgot to call writeLOC()?"); + if ( m_pCurrentEntry->nMethod == DEFLATED && ( m_pCurrentEntry->nFlag & 8 ) ) + writeEXT(*m_pCurrentEntry); + + if (bEncrypt) + m_pCurrentEntry->nMethod = STORED; + + m_pCurrentEntry = nullptr; +} + +void ZipOutputStream::consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread> pCandidate) +{ + //Any exceptions thrown in the threads were caught and stored for now + const std::exception_ptr& rCaughtException(pCandidate->getParallelDeflateException()); + if (rCaughtException) + { + m_aDeflateException = rCaughtException; // store it for later throwing + // the exception handler in DeflateThreadTask should have cleaned temp file + return; + } + + writeLOC(pCandidate->getZipEntry(), pCandidate->isEncrypt()); + + sal_Int32 nRead; + uno::Sequence< sal_Int8 > aSequence(n_ConstBufferSize); + uno::Reference< io::XInputStream > xInput = pCandidate->getData(); + do + { + nRead = xInput->readBytes(aSequence, n_ConstBufferSize); + if (nRead < n_ConstBufferSize) + aSequence.realloc(nRead); + + rawWrite(aSequence); + } + while (nRead == n_ConstBufferSize); + xInput.clear(); + + rawCloseEntry(pCandidate->isEncrypt()); + + pCandidate->getZipPackageStream()->successfullyWritten(pCandidate->getZipEntry()); + pCandidate->deleteBufferFile(); +} + +void ZipOutputStream::consumeFinishedScheduledThreadTaskEntries() +{ + std::vector< ZipOutputEntryInThread* > aNonFinishedEntries; + + for(ZipOutputEntryInThread* pEntry : m_aEntries) + { + if(pEntry->isFinished()) + { + consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pEntry)); + } + else + { + aNonFinishedEntries.push_back(pEntry); + } + } + + // always reset to non-consumed entries + m_aEntries = aNonFinishedEntries; +} + +void ZipOutputStream::reduceScheduledThreadTasksToGivenNumberOrLess(std::size_t nThreadTasks) +{ + while(m_aEntries.size() > nThreadTasks) + { + consumeFinishedScheduledThreadTaskEntries(); + + if(m_aEntries.size() > nThreadTasks) + { + osl::Thread::wait(std::chrono::microseconds(100)); + } + } +} + +void ZipOutputStream::finish() +{ + assert(!m_aZipList.empty() && "Zip file must have at least one entry!"); + + // Wait for all thread tasks to finish & write + comphelper::ThreadPool::getSharedOptimalPool().waitUntilDone(mpThreadTaskTag); + + // consume all processed entries + while(!m_aEntries.empty()) + { + ZipOutputEntryInThread* pCandidate = m_aEntries.back(); + m_aEntries.pop_back(); + consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pCandidate)); + } + + sal_Int32 nOffset= static_cast < sal_Int32 > (m_aChucker.GetPosition()); + for (ZipEntry* p : m_aZipList) + { + writeCEN( *p ); + delete p; + } + writeEND( nOffset, static_cast < sal_Int32 > (m_aChucker.GetPosition()) - nOffset); + m_xStream->flush(); + m_aZipList.clear(); + + if (m_aDeflateException) + { // throw once all thread tasks are finished and m_aEntries can be released + std::rethrow_exception(m_aDeflateException); + } +} + +const css::uno::Reference< css::io::XOutputStream >& ZipOutputStream::getStream() const +{ + return m_xStream; +} + +void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength) +{ + m_aChucker.WriteInt32( ENDSIG ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( m_aZipList.size() ); + m_aChucker.WriteInt16( m_aZipList.size() ); + m_aChucker.WriteUInt32( nLength ); + m_aChucker.WriteUInt32( nOffset ); + m_aChucker.WriteInt16( 0 ); +} + +static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated ) +{ + if( nNum >= 0xffffffff ) + { + *pIsTruncated = true; + return 0xffffffff; + } + else + return static_cast< sal_uInt32 >( nNum ); +} + +void ZipOutputStream::writeCEN( const ZipEntry &rEntry ) +{ + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) + throw IOException("Unexpected character is used in file name." ); + + OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); + sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); + + m_aChucker.WriteInt32( CENSIG ); + m_aChucker.WriteInt16( rEntry.nVersion ); + m_aChucker.WriteInt16( rEntry.nVersion ); + m_aChucker.WriteInt16( rEntry.nFlag ); + m_aChucker.WriteInt16( rEntry.nMethod ); + bool bWrite64Header = false; + + m_aChucker.WriteUInt32( rEntry.nTime ); + m_aChucker.WriteUInt32( rEntry.nCrc ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) ); + m_aChucker.WriteInt16( nNameLength ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nOffset, &bWrite64Header ) ); + + if( bWrite64Header ) + { + // FIXME64: need to append a ZIP64 header instead of throwing + // We're about to silently lose people's data - which they are + // unlikely to appreciate so fail instead: + throw IOException( "File contains streams that are too large." ); + } + + Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() ); + m_aChucker.WriteBytes( aSequence ); +} + +void ZipOutputStream::writeEXT( const ZipEntry &rEntry ) +{ + bool bWrite64Header = false; + + m_aChucker.WriteInt32( EXTSIG ); + m_aChucker.WriteUInt32( rEntry.nCrc ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) ); + + if( bWrite64Header ) + { + // FIXME64: need to append a ZIP64 header instead of throwing + // We're about to silently lose people's data - which they are + // unlikely to appreciate so fail instead: + throw IOException( "File contains streams that are too large." ); + } +} + +void ZipOutputStream::writeLOC( ZipEntry *pEntry, bool bEncrypt ) +{ + assert(!m_pCurrentEntry && "Forgot to close an entry with rawCloseEntry()?"); + m_pCurrentEntry = pEntry; + m_aZipList.push_back( m_pCurrentEntry ); + const ZipEntry &rEntry = *m_pCurrentEntry; + + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) + throw IOException("Unexpected character is used in file name." ); + + OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); + sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); + + m_aChucker.WriteInt32( LOCSIG ); + m_aChucker.WriteInt16( rEntry.nVersion ); + + m_aChucker.WriteInt16( rEntry.nFlag ); + // If it's an encrypted entry, we pretend its stored plain text + if (bEncrypt) + m_aChucker.WriteInt16( STORED ); + else + m_aChucker.WriteInt16( rEntry.nMethod ); + + bool bWrite64Header = false; + + m_aChucker.WriteUInt32( rEntry.nTime ); + if ((rEntry.nFlag & 8) == 8 ) + { + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteInt32( 0 ); + } + else + { + m_aChucker.WriteUInt32( rEntry.nCrc ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) ); + } + m_aChucker.WriteInt16( nNameLength ); + m_aChucker.WriteInt16( 0 ); + + if( bWrite64Header ) + { + // FIXME64: need to append a ZIP64 header instead of throwing + // We're about to silently lose people's data - which they are + // unlikely to appreciate so fail instead: + throw IOException( "File contains streams that are too large." ); + } + + Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() ); + m_aChucker.WriteBytes( aSequence ); + + m_pCurrentEntry->nOffset = m_aChucker.GetPosition() - (LOCHDR + nNameLength); +} + +sal_uInt32 ZipOutputStream::getCurrentDosTime() +{ + oslDateTime aDateTime; + TimeValue aTimeValue; + osl_getSystemTime ( &aTimeValue ); + osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime); + + // at year 2108, there is an overflow + // -> some decision needs to be made + // how to handle the ZIP file format (just overflow?) + + // if the current system time is before 1980, + // then the time traveller will have to make a decision + // how to handle the ZIP file format before it is invented + // (just underflow?) + + assert(aDateTime.Year > 1980 && aDateTime.Year < 2108); + + sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year); + + if (nYear>=1980) + nYear-=1980; + else if (nYear>=80) + { + nYear-=80; + } + sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) + + ( 32 * (aDateTime.Month)) + + ( 512 * nYear ) ) << 16) | + ( ( aDateTime.Seconds/2) + + ( 32 * aDateTime.Minutes) + + ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) ); + return nResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/blowfishcontext.cxx b/package/source/zipapi/blowfishcontext.cxx new file mode 100644 index 000000000..1b5ed4a14 --- /dev/null +++ b/package/source/zipapi/blowfishcontext.cxx @@ -0,0 +1,109 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <rtl/cipher.h> +#include <rtl/ref.hxx> + +#include "blowfishcontext.hxx" + +using namespace ::com::sun::star; + +// static +uno::Reference< xml::crypto::XCipherContext > BlowfishCFB8CipherContext::Create( const uno::Sequence< sal_Int8 >& aDerivedKey, const uno::Sequence< sal_Int8 >& aInitVector, bool bEncrypt ) +{ + ::rtl::Reference< BlowfishCFB8CipherContext > xResult = new BlowfishCFB8CipherContext(); + xResult->m_pCipher = rtl_cipher_create( rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream ); + if ( !xResult->m_pCipher ) + throw uno::RuntimeException("Can not create cipher!" ); + + if ( rtl_Cipher_E_None != rtl_cipher_init( + xResult->m_pCipher, + bEncrypt ? rtl_Cipher_DirectionEncode : rtl_Cipher_DirectionDecode, + reinterpret_cast< const sal_uInt8* >( aDerivedKey.getConstArray() ), + aDerivedKey.getLength(), + reinterpret_cast< const sal_uInt8* >( aInitVector.getConstArray() ), + aInitVector.getLength() ) ) + { + throw uno::RuntimeException("Can not initialize cipher!" ); + } + + xResult->m_bEncrypt = bEncrypt; + + return xResult; +} + +BlowfishCFB8CipherContext::~BlowfishCFB8CipherContext() +{ + if ( m_pCipher ) + { + rtl_cipher_destroy ( m_pCipher ); + m_pCipher = nullptr; + } +} + +uno::Sequence< sal_Int8 > SAL_CALL BlowfishCFB8CipherContext::convertWithCipherContext( const uno::Sequence< ::sal_Int8 >& aData ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pCipher ) + throw lang::DisposedException(); + + uno::Sequence< sal_Int8 > aResult( aData.getLength() ); + rtlCipherError nError = rtl_Cipher_E_None; + + if ( m_bEncrypt ) + { + nError = rtl_cipher_encode( m_pCipher, + aData.getConstArray(), + aData.getLength(), + reinterpret_cast< sal_uInt8* >( aResult.getArray() ), + aResult.getLength() ); + } + else + { + nError = rtl_cipher_decode( m_pCipher, + aData.getConstArray(), + aData.getLength(), + reinterpret_cast< sal_uInt8* >( aResult.getArray() ), + aResult.getLength() ); + } + + if ( rtl_Cipher_E_None != nError ) + { + throw uno::RuntimeException("Can not decrypt/encrypt with cipher!" ); + } + + return aResult; +} + +uno::Sequence< ::sal_Int8 > SAL_CALL BlowfishCFB8CipherContext::finalizeCipherContextAndDispose() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pCipher ) + throw lang::DisposedException(); + + rtl_cipher_destroy ( m_pCipher ); + m_pCipher = nullptr; + + return uno::Sequence< sal_Int8 >(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/blowfishcontext.hxx b/package/source/zipapi/blowfishcontext.hxx new file mode 100644 index 000000000..c0b603c15 --- /dev/null +++ b/package/source/zipapi/blowfishcontext.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX + +#include <com/sun/star/xml/crypto/XCipherContext.hpp> + +#include <cppuhelper/implbase.hxx> +#include <mutex> + +class BlowfishCFB8CipherContext : public cppu::WeakImplHelper< css::xml::crypto::XCipherContext > +{ + std::mutex m_aMutex; + void* m_pCipher; + bool m_bEncrypt; + + BlowfishCFB8CipherContext() + : m_pCipher( nullptr ) + , m_bEncrypt( false ) + {} + +public: + + virtual ~BlowfishCFB8CipherContext() override; + + static css::uno::Reference< css::xml::crypto::XCipherContext > + Create( const css::uno::Sequence< sal_Int8 >& aDerivedKey, const css::uno::Sequence< sal_Int8 >& aInitVector, bool bEncrypt ); + + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertWithCipherContext( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL finalizeCipherContextAndDispose( ) override; +}; + +#endif // INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/sha1context.cxx b/package/source/zipapi/sha1context.cxx new file mode 100644 index 000000000..4b6cbd2d3 --- /dev/null +++ b/package/source/zipapi/sha1context.cxx @@ -0,0 +1,119 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <rtl/digest.h> +#include <rtl/ref.hxx> + +#include "sha1context.hxx" + +using namespace ::com::sun::star; + +// static +uno::Reference<xml::crypto::XDigestContext> StarOfficeSHA1DigestContext::Create() +{ + ::rtl::Reference<StarOfficeSHA1DigestContext> xResult = new StarOfficeSHA1DigestContext(); + xResult->m_pDigest = rtl_digest_createSHA1(); + if ( !xResult->m_pDigest ) + throw uno::RuntimeException("Can not create cipher!" ); + + return xResult; +} + +StarOfficeSHA1DigestContext::~StarOfficeSHA1DigestContext() +{ + if ( m_pDigest ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + } +} + +void SAL_CALL StarOfficeSHA1DigestContext::updateDigest(const uno::Sequence<::sal_Int8>& aData) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pDigest ) + throw lang::DisposedException(); + + if ( rtl_Digest_E_None != rtl_digest_updateSHA1( m_pDigest, aData.getConstArray(), aData.getLength() ) ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + throw uno::RuntimeException(); + } +} + +uno::Sequence<::sal_Int8> SAL_CALL StarOfficeSHA1DigestContext::finalizeDigestAndDispose() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pDigest ) + throw lang::DisposedException(); + + uno::Sequence< sal_Int8 > aResult( RTL_DIGEST_LENGTH_SHA1 ); + if ( rtl_Digest_E_None != rtl_digest_getSHA1( m_pDigest, reinterpret_cast< sal_uInt8* >( aResult.getArray() ), aResult.getLength() ) ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + throw uno::RuntimeException(); + } + + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + return aResult; +} + +uno::Reference<xml::crypto::XDigestContext> CorrectSHA1DigestContext::Create() +{ + return new CorrectSHA1DigestContext(); +} + +CorrectSHA1DigestContext::CorrectSHA1DigestContext() +{ +} + +CorrectSHA1DigestContext::~CorrectSHA1DigestContext() +{ +} + +void SAL_CALL CorrectSHA1DigestContext::updateDigest(const uno::Sequence<::sal_Int8>& rData) +{ + std::scoped_lock aGuard(m_Mutex); + if (m_bDisposed) + throw lang::DisposedException(); + + m_Hash.update(reinterpret_cast<unsigned char const*>(rData.getConstArray()), rData.getLength()); +} + +uno::Sequence<::sal_Int8> SAL_CALL CorrectSHA1DigestContext::finalizeDigestAndDispose() +{ + std::scoped_lock aGuard(m_Mutex); + if (m_bDisposed) + throw lang::DisposedException(); + + m_bDisposed = true; + std::vector<unsigned char> const sha1(m_Hash.finalize()); + return uno::Sequence<sal_Int8>(reinterpret_cast<sal_Int8 const*>(sha1.data()), sha1.size()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/sha1context.hxx b/package/source/zipapi/sha1context.hxx new file mode 100644 index 000000000..6cc09da01 --- /dev/null +++ b/package/source/zipapi/sha1context.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_SHA1CONTEXT_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_SHA1CONTEXT_HXX + +#include <com/sun/star/xml/crypto/XDigestContext.hpp> + +#include <comphelper/hash.hxx> +#include <cppuhelper/implbase.hxx> +#include <mutex> + +class StarOfficeSHA1DigestContext + : public cppu::WeakImplHelper<css::xml::crypto::XDigestContext> +{ + std::mutex m_aMutex; + void* m_pDigest; + + StarOfficeSHA1DigestContext() + : m_pDigest( nullptr ) + {} + +public: + + virtual ~StarOfficeSHA1DigestContext() override; + + static css::uno::Reference< css::xml::crypto::XDigestContext > Create(); + + virtual void SAL_CALL updateDigest( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL finalizeDigestAndDispose() override; + +}; + +class CorrectSHA1DigestContext + : public cppu::WeakImplHelper<css::xml::crypto::XDigestContext> +{ + std::mutex m_Mutex; + ::comphelper::Hash m_Hash{::comphelper::HashType::SHA1}; + bool m_bDisposed{false}; + + CorrectSHA1DigestContext(); + +public: + + virtual ~CorrectSHA1DigestContext() override; + + static css::uno::Reference<css::xml::crypto::XDigestContext> Create(); + + virtual void SAL_CALL updateDigest(const css::uno::Sequence<::sal_Int8>& rData) override; + virtual css::uno::Sequence<::sal_Int8> SAL_CALL finalizeDigestAndDispose() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |