| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_package.hxx" |
| |
| #include <com/sun/star/packages/zip/ZipConstants.hpp> |
| #include <com/sun/star/io/XOutputStream.hpp> |
| #include <comphelper/storagehelper.hxx> |
| |
| #include <osl/time.h> |
| |
| #include <EncryptionData.hxx> |
| #include <PackageConstants.hxx> |
| #include <ZipEntry.hxx> |
| #include <ZipFile.hxx> |
| #include <ZipPackageStream.hxx> |
| #include <ZipOutputStream.hxx> |
| |
| using namespace rtl; |
| using namespace com::sun::star; |
| using namespace com::sun::star::io; |
| using namespace com::sun::star::uno; |
| using namespace com::sun::star::packages; |
| using namespace com::sun::star::packages::zip; |
| using namespace com::sun::star::packages::zip::ZipConstants; |
| |
| /** This class is used to write Zip files |
| */ |
| ZipOutputStream::ZipOutputStream( const uno::Reference< lang::XMultiServiceFactory >& xFactory, |
| const uno::Reference < XOutputStream > &xOStream ) |
| : m_xFactory( xFactory ) |
| , xStream(xOStream) |
| , m_aDeflateBuffer(n_ConstBufferSize) |
| , aDeflater(DEFAULT_COMPRESSION, sal_True) |
| , aChucker(xOStream) |
| , pCurrentEntry(NULL) |
| , nMethod(DEFLATED) |
| , bFinished(sal_False) |
| , bEncryptCurrentEntry(sal_False) |
| , m_pCurrentStream(NULL) |
| { |
| } |
| |
| ZipOutputStream::~ZipOutputStream( void ) |
| { |
| for (sal_Int32 i = 0, nEnd = aZipList.size(); i < nEnd; i++) |
| delete aZipList[i]; |
| } |
| |
| void SAL_CALL ZipOutputStream::setMethod( sal_Int32 nNewMethod ) |
| throw(RuntimeException) |
| { |
| nMethod = static_cast < sal_Int16 > (nNewMethod); |
| } |
| void SAL_CALL ZipOutputStream::setLevel( sal_Int32 nNewLevel ) |
| throw(RuntimeException) |
| { |
| aDeflater.setLevel( nNewLevel); |
| } |
| |
| void SAL_CALL ZipOutputStream::putNextEntry( ZipEntry& rEntry, |
| ZipPackageStream* pStream, |
| sal_Bool bEncrypt) |
| throw(IOException, RuntimeException) |
| { |
| if (pCurrentEntry != NULL) |
| closeEntry(); |
| if (rEntry.nTime == -1) |
| rEntry.nTime = getCurrentDosTime(); |
| if (rEntry.nMethod == -1) |
| rEntry.nMethod = nMethod; |
| rEntry.nVersion = 20; |
| rEntry.nFlag = 1 << 11; |
| if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 || |
| rEntry.nCrc == -1) |
| { |
| rEntry.nSize = rEntry.nCompressedSize = 0; |
| rEntry.nFlag |= 8; |
| } |
| |
| if (bEncrypt) |
| { |
| bEncryptCurrentEntry = sal_True; |
| |
| m_xCipherContext = ZipFile::StaticGetCipher( m_xFactory, pStream->GetEncryptionData(), true ); |
| m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xFactory, pStream->GetEncryptionData() ); |
| mnDigested = 0; |
| rEntry.nFlag |= 1 << 4; |
| m_pCurrentStream = pStream; |
| } |
| sal_Int32 nLOCLength = writeLOC(rEntry); |
| rEntry.nOffset = static_cast < sal_Int32 > (aChucker.GetPosition()) - nLOCLength; |
| aZipList.push_back( &rEntry ); |
| pCurrentEntry = &rEntry; |
| } |
| |
| void SAL_CALL ZipOutputStream::closeEntry( ) |
| throw(IOException, RuntimeException) |
| { |
| ZipEntry *pEntry = pCurrentEntry; |
| if (pEntry) |
| { |
| switch (pEntry->nMethod) |
| { |
| case DEFLATED: |
| aDeflater.finish(); |
| while (!aDeflater.finished()) |
| doDeflate(); |
| if ((pEntry->nFlag & 8) == 0) |
| { |
| if (pEntry->nSize != aDeflater.getTotalIn()) |
| { |
| OSL_ENSURE(false,"Invalid entry size"); |
| } |
| if (pEntry->nCompressedSize != aDeflater.getTotalOut()) |
| { |
| //VOS_DEBUG_ONLY("Invalid entry compressed size"); |
| // Different compression strategies make the merit of this |
| // test somewhat dubious |
| pEntry->nCompressedSize = aDeflater.getTotalOut(); |
| } |
| if (pEntry->nCrc != aCRC.getValue()) |
| { |
| OSL_ENSURE(false,"Invalid entry CRC-32"); |
| } |
| } |
| else |
| { |
| if ( !bEncryptCurrentEntry ) |
| { |
| pEntry->nSize = aDeflater.getTotalIn(); |
| pEntry->nCompressedSize = aDeflater.getTotalOut(); |
| } |
| pEntry->nCrc = aCRC.getValue(); |
| writeEXT(*pEntry); |
| } |
| aDeflater.reset(); |
| aCRC.reset(); |
| break; |
| case STORED: |
| if (!((pEntry->nFlag & 8) == 0)) |
| OSL_ENSURE ( false, "Serious error, one of compressed size, size or CRC was -1 in a STORED stream"); |
| break; |
| default: |
| OSL_ENSURE(false,"Invalid compression method"); |
| break; |
| } |
| |
| if (bEncryptCurrentEntry) |
| { |
| bEncryptCurrentEntry = sal_False; |
| |
| 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 ); |
| } |
| pCurrentEntry = NULL; |
| m_pCurrentStream = NULL; |
| } |
| } |
| |
| void SAL_CALL ZipOutputStream::write( const Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength ) |
| throw(IOException, RuntimeException) |
| { |
| switch (pCurrentEntry->nMethod) |
| { |
| case DEFLATED: |
| if (!aDeflater.finished()) |
| { |
| aDeflater.setInputSegment(rBuffer, nNewOffset, nNewLength); |
| while (!aDeflater.needsInput()) |
| doDeflate(); |
| if (!bEncryptCurrentEntry) |
| aCRC.updateSegment(rBuffer, nNewOffset, nNewLength); |
| } |
| break; |
| case STORED: |
| { |
| Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength ); |
| aChucker.WriteBytes( aTmpBuffer ); |
| } |
| break; |
| } |
| } |
| |
| void SAL_CALL ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength ) |
| throw(IOException, RuntimeException) |
| { |
| Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength ); |
| aChucker.WriteBytes( aTmpBuffer ); |
| } |
| |
| void SAL_CALL ZipOutputStream::rawCloseEntry( ) |
| throw(IOException, RuntimeException) |
| { |
| if ( pCurrentEntry->nMethod == DEFLATED && ( pCurrentEntry->nFlag & 8 ) ) |
| writeEXT(*pCurrentEntry); |
| pCurrentEntry = NULL; |
| } |
| |
| void SAL_CALL ZipOutputStream::finish( ) |
| throw(IOException, RuntimeException) |
| { |
| if (bFinished) |
| return; |
| |
| if (pCurrentEntry != NULL) |
| closeEntry(); |
| |
| if (aZipList.size() < 1) |
| OSL_ENSURE(false,"Zip file must have at least one entry!\n"); |
| |
| sal_Int32 nOffset= static_cast < sal_Int32 > (aChucker.GetPosition()); |
| for (sal_Int32 i =0, nEnd = aZipList.size(); i < nEnd; i++) |
| writeCEN( *aZipList[i] ); |
| writeEND( nOffset, static_cast < sal_Int32 > (aChucker.GetPosition()) - nOffset); |
| bFinished = sal_True; |
| xStream->flush(); |
| } |
| |
| void ZipOutputStream::doDeflate() |
| { |
| sal_Int32 nLength = aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength()); |
| |
| if ( nLength > 0 ) |
| { |
| uno::Sequence< sal_Int8 > aTmpBuffer( m_aDeflateBuffer.getConstArray(), nLength ); |
| if ( bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() ) |
| { |
| // Need to update our digest before encryption... |
| sal_Int32 nDiff = n_ConstDigestLength - mnDigested; |
| if ( nDiff ) |
| { |
| sal_Int32 nEat = ::std::min( nLength, nDiff ); |
| uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat ); |
| m_xDigestContext->updateDigest( aTmpSeq ); |
| mnDigested = mnDigested + static_cast< sal_Int16 >( nEat ); |
| } |
| |
| uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer ); |
| |
| aChucker.WriteBytes( aEncryptionBuffer ); |
| |
| // the sizes as well as checksum for encrypted streams is calculated here |
| pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); |
| pCurrentEntry->nSize = pCurrentEntry->nCompressedSize; |
| aCRC.update( aEncryptionBuffer ); |
| } |
| else |
| { |
| aChucker.WriteBytes ( aTmpBuffer ); |
| } |
| } |
| |
| if ( aDeflater.finished() && bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() ) |
| { |
| uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose(); |
| if ( aEncryptionBuffer.getLength() ) |
| { |
| aChucker.WriteBytes( aEncryptionBuffer ); |
| |
| // the sizes as well as checksum for encrypted streams is calculated hier |
| pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); |
| pCurrentEntry->nSize = pCurrentEntry->nCompressedSize; |
| aCRC.update( aEncryptionBuffer ); |
| } |
| } |
| } |
| |
| void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength) |
| throw(IOException, RuntimeException) |
| { |
| aChucker << ENDSIG; |
| aChucker << static_cast < sal_Int16 > ( 0 ); |
| aChucker << static_cast < sal_Int16 > ( 0 ); |
| aChucker << static_cast < sal_Int16 > ( aZipList.size() ); |
| aChucker << static_cast < sal_Int16 > ( aZipList.size() ); |
| aChucker << nLength; |
| aChucker << nOffset; |
| aChucker << static_cast < sal_Int16 > ( 0 ); |
| } |
| void ZipOutputStream::writeCEN( const ZipEntry &rEntry ) |
| throw(IOException, RuntimeException) |
| { |
| if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) ) |
| throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() ); |
| |
| ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); |
| sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); |
| |
| aChucker << CENSIG; |
| aChucker << rEntry.nVersion; |
| aChucker << rEntry.nVersion; |
| if (rEntry.nFlag & (1 << 4) ) |
| { |
| // If it's an encrypted entry, we pretend its stored plain text |
| ZipEntry *pEntry = const_cast < ZipEntry * > ( &rEntry ); |
| pEntry->nFlag &= ~(1 <<4 ); |
| aChucker << rEntry.nFlag; |
| aChucker << static_cast < sal_Int16 > ( STORED ); |
| } |
| else |
| { |
| aChucker << rEntry.nFlag; |
| aChucker << rEntry.nMethod; |
| } |
| aChucker << static_cast < sal_uInt32> ( rEntry.nTime ); |
| aChucker << static_cast < sal_uInt32> ( rEntry.nCrc ); |
| aChucker << rEntry.nCompressedSize; |
| aChucker << rEntry.nSize; |
| aChucker << nNameLength; |
| aChucker << static_cast < sal_Int16> (0); |
| aChucker << static_cast < sal_Int16> (0); |
| aChucker << static_cast < sal_Int16> (0); |
| aChucker << static_cast < sal_Int16> (0); |
| aChucker << static_cast < sal_Int32> (0); |
| aChucker << rEntry.nOffset; |
| |
| Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() ); |
| aChucker.WriteBytes( aSequence ); |
| } |
| void ZipOutputStream::writeEXT( const ZipEntry &rEntry ) |
| throw(IOException, RuntimeException) |
| { |
| aChucker << EXTSIG; |
| aChucker << static_cast < sal_uInt32> ( rEntry.nCrc ); |
| aChucker << rEntry.nCompressedSize; |
| aChucker << rEntry.nSize; |
| } |
| |
| sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry ) |
| throw(IOException, RuntimeException) |
| { |
| if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) ) |
| throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() ); |
| |
| ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); |
| sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); |
| |
| aChucker << LOCSIG; |
| aChucker << rEntry.nVersion; |
| |
| if (rEntry.nFlag & (1 << 4) ) |
| { |
| // If it's an encrypted entry, we pretend its stored plain text |
| sal_Int16 nTmpFlag = rEntry.nFlag; |
| nTmpFlag &= ~(1 <<4 ); |
| aChucker << nTmpFlag; |
| aChucker << static_cast < sal_Int16 > ( STORED ); |
| } |
| else |
| { |
| aChucker << rEntry.nFlag; |
| aChucker << rEntry.nMethod; |
| } |
| |
| aChucker << static_cast < sal_uInt32 > (rEntry.nTime); |
| if ((rEntry.nFlag & 8) == 8 ) |
| { |
| aChucker << static_cast < sal_Int32 > (0); |
| aChucker << static_cast < sal_Int32 > (0); |
| aChucker << static_cast < sal_Int32 > (0); |
| } |
| else |
| { |
| aChucker << static_cast < sal_uInt32 > (rEntry.nCrc); |
| aChucker << rEntry.nCompressedSize; |
| aChucker << rEntry.nSize; |
| } |
| aChucker << nNameLength; |
| aChucker << static_cast < sal_Int16 > (0); |
| |
| Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() ); |
| aChucker.WriteBytes( aSequence ); |
| |
| return LOCHDR + nNameLength; |
| } |
| sal_uInt32 ZipOutputStream::getCurrentDosTime( ) |
| { |
| oslDateTime aDateTime; |
| TimeValue aTimeValue; |
| osl_getSystemTime ( &aTimeValue ); |
| osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime); |
| |
| 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; |
| } |
| /* |
| |
| This is actually never used, so I removed it, but thought that the |
| implementation details may be useful in the future...mtg 20010307 |
| |
| I stopped using the time library and used the OSL version instead, but |
| it might still be useful to have this code here.. |
| |
| void ZipOutputStream::dosDateToTMDate ( tm &rTime, sal_uInt32 nDosDate) |
| { |
| sal_uInt32 nDate = static_cast < sal_uInt32 > (nDosDate >> 16); |
| rTime.tm_mday = static_cast < sal_uInt32 > ( nDate & 0x1F); |
| rTime.tm_mon = static_cast < sal_uInt32 > ( ( ( (nDate) & 0x1E0)/0x20)-1); |
| rTime.tm_year = static_cast < sal_uInt32 > ( ( (nDate & 0x0FE00)/0x0200)+1980); |
| |
| rTime.tm_hour = static_cast < sal_uInt32 > ( (nDosDate & 0xF800)/0x800); |
| rTime.tm_min = static_cast < sal_uInt32 > ( (nDosDate & 0x7E0)/0x20); |
| rTime.tm_sec = static_cast < sal_uInt32 > ( 2 * (nDosDate & 0x1F) ); |
| } |
| */ |
| |