xref: /trunk/main/package/source/package/zipapi/ZipOutputStream.cxx (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_package.hxx"
26 
27 #include <com/sun/star/packages/zip/ZipConstants.hpp>
28 #include <com/sun/star/io/XOutputStream.hpp>
29 #include <comphelper/storagehelper.hxx>
30 
31 #include <osl/time.h>
32 
33 #include <EncryptionData.hxx>
34 #include <PackageConstants.hxx>
35 #include <ZipEntry.hxx>
36 #include <ZipFile.hxx>
37 #include <ZipPackageStream.hxx>
38 #include <ZipOutputStream.hxx>
39 
40 using namespace rtl;
41 using namespace com::sun::star;
42 using namespace com::sun::star::io;
43 using namespace com::sun::star::uno;
44 using namespace com::sun::star::packages;
45 using namespace com::sun::star::packages::zip;
46 using namespace com::sun::star::packages::zip::ZipConstants;
47 
48 /** This class is used to write Zip files
49  */
ZipOutputStream(const uno::Reference<lang::XMultiServiceFactory> & xFactory,const uno::Reference<XOutputStream> & xOStream)50 ZipOutputStream::ZipOutputStream( const uno::Reference< lang::XMultiServiceFactory >& xFactory,
51                                   const uno::Reference < XOutputStream > &xOStream )
52 : m_xFactory( xFactory )
53 , xStream(xOStream)
54 , m_aDeflateBuffer(n_ConstBufferSize)
55 , aDeflater(DEFAULT_COMPRESSION, sal_True)
56 , aChucker(xOStream)
57 , pCurrentEntry(NULL)
58 , nMethod(DEFLATED)
59 , bFinished(sal_False)
60 , bEncryptCurrentEntry(sal_False)
61 , m_pCurrentStream(NULL)
62 {
63 }
64 
~ZipOutputStream(void)65 ZipOutputStream::~ZipOutputStream( void )
66 {
67     for (sal_Int32 i = 0, nEnd = aZipList.size(); i < nEnd; i++)
68         delete aZipList[i];
69 }
70 
setMethod(sal_Int32 nNewMethod)71 void SAL_CALL ZipOutputStream::setMethod( sal_Int32 nNewMethod )
72 {
73     nMethod = static_cast < sal_Int16 > (nNewMethod);
74 }
setLevel(sal_Int32 nNewLevel)75 void SAL_CALL ZipOutputStream::setLevel( sal_Int32 nNewLevel )
76 {
77     aDeflater.setLevel( nNewLevel);
78 }
79 
putNextEntry(ZipEntry & rEntry,ZipPackageStream * pStream,sal_Bool bEncrypt)80 void SAL_CALL ZipOutputStream::putNextEntry( ZipEntry& rEntry,
81                         ZipPackageStream* pStream,
82                         sal_Bool bEncrypt)
83 {
84     if (pCurrentEntry != NULL)
85         closeEntry();
86     if (rEntry.nTime == -1)
87         rEntry.nTime = getCurrentDosTime();
88     if (rEntry.nMethod == -1)
89         rEntry.nMethod = nMethod;
90     rEntry.nVersion = 20;
91     rEntry.nFlag = 1 << 11;
92     if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 ||
93         rEntry.nCrc == -1)
94     {
95         rEntry.nSize = rEntry.nCompressedSize = 0;
96         rEntry.nFlag |= 8;
97     }
98 
99     if (bEncrypt)
100     {
101         bEncryptCurrentEntry = sal_True;
102 
103         m_xCipherContext = ZipFile::StaticGetCipher( m_xFactory, pStream->GetEncryptionData(), true );
104         m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xFactory, pStream->GetEncryptionData() );
105         mnDigested = 0;
106         rEntry.nFlag |= 1 << 4;
107         m_pCurrentStream = pStream;
108     }
109     sal_Int32 nLOCLength = writeLOC(rEntry);
110     rEntry.nFileHeaderOffset = static_cast < sal_Int32 > (aChucker.GetPosition()) - nLOCLength;
111     aZipList.push_back( &rEntry );
112     pCurrentEntry = &rEntry;
113 }
114 
closeEntry()115 void SAL_CALL ZipOutputStream::closeEntry(  )
116 {
117     ZipEntry *pEntry = pCurrentEntry;
118     if (pEntry)
119     {
120         switch (pEntry->nMethod)
121         {
122             case DEFLATED:
123                 aDeflater.finish();
124                 while (!aDeflater.finished())
125                     doDeflate();
126                 if ((pEntry->nFlag & 8) == 0)
127                 {
128                     if (pEntry->nSize != aDeflater.getTotalIn())
129                     {
130                         OSL_ENSURE(false,"Invalid entry size");
131                     }
132                     if (pEntry->nCompressedSize != aDeflater.getTotalOut())
133                     {
134                         //VOS_DEBUG_ONLY("Invalid entry compressed size");
135                         // Different compression strategies make the merit of this
136                         // test somewhat dubious
137                         pEntry->nCompressedSize = aDeflater.getTotalOut();
138                     }
139                     if (pEntry->nCrc != aCRC.getValue())
140                     {
141                         OSL_ENSURE(false,"Invalid entry CRC-32");
142                     }
143                 }
144                 else
145                 {
146                     if ( !bEncryptCurrentEntry )
147                     {
148                         pEntry->nSize = aDeflater.getTotalIn();
149                         pEntry->nCompressedSize = aDeflater.getTotalOut();
150                     }
151                     pEntry->nCrc = aCRC.getValue();
152                     writeEXT(*pEntry);
153                 }
154                 aDeflater.reset();
155                 aCRC.reset();
156                 break;
157             case STORED:
158                 if (!((pEntry->nFlag & 8) == 0))
159                     OSL_ENSURE ( false, "Serious error, one of compressed size, size or CRC was -1 in a STORED stream");
160                 break;
161             default:
162                 OSL_ENSURE(false,"Invalid compression method");
163                 break;
164         }
165 
166         if (bEncryptCurrentEntry)
167         {
168             bEncryptCurrentEntry = sal_False;
169 
170             m_xCipherContext.clear();
171 
172             uno::Sequence< sal_Int8 > aDigestSeq;
173             if ( m_xDigestContext.is() )
174             {
175                 aDigestSeq = m_xDigestContext->finalizeDigestAndDispose();
176                 m_xDigestContext.clear();
177             }
178 
179             if ( m_pCurrentStream )
180                 m_pCurrentStream->setDigest( aDigestSeq );
181         }
182         pCurrentEntry = NULL;
183         m_pCurrentStream = NULL;
184     }
185 }
186 
write(const Sequence<sal_Int8> & rBuffer,sal_Int32 nNewOffset,sal_Int32 nNewLength)187 void SAL_CALL ZipOutputStream::write( const Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength )
188 {
189     switch (pCurrentEntry->nMethod)
190     {
191         case DEFLATED:
192             if (!aDeflater.finished())
193             {
194                 aDeflater.setInputSegment(rBuffer, nNewOffset, nNewLength);
195                 while (!aDeflater.needsInput())
196                     doDeflate();
197                 if (!bEncryptCurrentEntry)
198                     aCRC.updateSegment(rBuffer, nNewOffset, nNewLength);
199             }
200             break;
201         case STORED:
202             {
203                 Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
204                 aChucker.WriteBytes( aTmpBuffer );
205             }
206             break;
207     }
208 }
209 
rawWrite(Sequence<sal_Int8> & rBuffer,sal_Int32,sal_Int32 nNewLength)210 void SAL_CALL ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength )
211 {
212     Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
213     aChucker.WriteBytes( aTmpBuffer );
214 }
215 
rawCloseEntry()216 void SAL_CALL ZipOutputStream::rawCloseEntry(  )
217 {
218     if ( pCurrentEntry->nMethod == DEFLATED && ( pCurrentEntry->nFlag & 8 ) )
219         writeEXT(*pCurrentEntry);
220     pCurrentEntry = NULL;
221 }
222 
finish()223 void SAL_CALL ZipOutputStream::finish(  )
224 {
225     if (bFinished)
226         return;
227 
228     if (pCurrentEntry != NULL)
229         closeEntry();
230 
231     if (aZipList.size() < 1)
232         OSL_ENSURE(false,"Zip file must have at least one entry!\n");
233 
234     sal_Int32 nCENOffset= static_cast < sal_Int32 > (aChucker.GetPosition());
235     for (sal_Int32 i =0, nEnd = aZipList.size(); i < nEnd; i++)
236         writeCEN( *aZipList[i] );
237     writeEND( nCENOffset, static_cast < sal_Int32 > (aChucker.GetPosition()) - nCENOffset);
238     bFinished = sal_True;
239     xStream->flush();
240 }
241 
doDeflate()242 void ZipOutputStream::doDeflate()
243 {
244     sal_Int32 nLength = aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength());
245 
246     if ( nLength > 0 )
247     {
248         uno::Sequence< sal_Int8 > aTmpBuffer( m_aDeflateBuffer.getConstArray(), nLength );
249         if ( bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
250         {
251             // Need to update our digest before encryption...
252             sal_Int32 nDiff = n_ConstDigestLength - mnDigested;
253             if ( nDiff )
254             {
255                 sal_Int32 nEat = ::std::min( nLength, nDiff );
256                 uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat );
257                 m_xDigestContext->updateDigest( aTmpSeq );
258                 mnDigested = mnDigested + static_cast< sal_Int16 >( nEat );
259             }
260 
261             uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer );
262 
263             aChucker.WriteBytes( aEncryptionBuffer );
264 
265             // the sizes as well as checksum for encrypted streams is calculated here
266             pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
267             pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
268             aCRC.update( aEncryptionBuffer );
269         }
270         else
271         {
272             aChucker.WriteBytes ( aTmpBuffer );
273         }
274     }
275 
276     if ( aDeflater.finished() && bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
277     {
278         uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose();
279         if ( aEncryptionBuffer.getLength() )
280         {
281             aChucker.WriteBytes( aEncryptionBuffer );
282 
283             // the sizes as well as checksum for encrypted streams is calculated hier
284             pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
285             pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
286             aCRC.update( aEncryptionBuffer );
287         }
288     }
289 }
290 
writeEND(sal_uInt32 nCENOffset,sal_uInt32 nLength)291 void ZipOutputStream::writeEND(sal_uInt32 nCENOffset, sal_uInt32 nLength)
292 {
293     aChucker << ENDSIG;
294     aChucker << static_cast < sal_Int16 > ( 0 );
295     aChucker << static_cast < sal_Int16 > ( 0 );
296     aChucker << static_cast < sal_Int16 > ( aZipList.size() );
297     aChucker << static_cast < sal_Int16 > ( aZipList.size() );
298     aChucker << nLength;
299     aChucker << nCENOffset;
300     aChucker << static_cast < sal_Int16 > ( 0 );
301 }
writeCEN(const ZipEntry & rEntry)302 void ZipOutputStream::writeCEN( const ZipEntry &rEntry )
303 {
304     if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
305         throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() );
306 
307     ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
308     sal_Int16 nNameLength       = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
309 
310     aChucker << CENSIG;
311     aChucker << rEntry.nVersion;
312     aChucker << rEntry.nVersion;
313     if (rEntry.nFlag & (1 << 4) )
314     {
315         // If it's an encrypted entry, we pretend its stored plain text
316         ZipEntry *pEntry = const_cast < ZipEntry * > ( &rEntry );
317         pEntry->nFlag &= ~(1 <<4 );
318         aChucker << rEntry.nFlag;
319         aChucker << static_cast < sal_Int16 > ( STORED );
320     }
321     else
322     {
323         aChucker << rEntry.nFlag;
324         aChucker << rEntry.nMethod;
325     }
326     aChucker << static_cast < sal_uInt32> ( rEntry.nTime );
327     aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
328     aChucker << rEntry.nCompressedSize;
329     aChucker << rEntry.nSize;
330     aChucker << nNameLength;
331     aChucker << static_cast < sal_Int16> (0); // extra field length
332     aChucker << static_cast < sal_Int16> (0); // file comment length
333     aChucker << static_cast < sal_Int16> (0); // disk number start
334     aChucker << static_cast < sal_Int16> (0); // internal file attributes
335     aChucker << static_cast < sal_Int32> (0); // external file attributes
336     aChucker << rEntry.nFileHeaderOffset;
337 
338     Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
339     aChucker.WriteBytes( aSequence );
340 }
writeEXT(const ZipEntry & rEntry)341 void ZipOutputStream::writeEXT( const ZipEntry &rEntry )
342 {
343     aChucker << EXTSIG;
344     aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
345     aChucker << rEntry.nCompressedSize;
346     aChucker << rEntry.nSize;
347 }
348 
writeLOC(const ZipEntry & rEntry)349 sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry )
350 {
351     if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
352         throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() );
353 
354     ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
355     sal_Int16 nNameLength       = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
356 
357     aChucker << LOCSIG;
358     aChucker << rEntry.nVersion; // version needed to extract
359 
360     if (rEntry.nFlag & (1 << 4) )
361     {
362         // If it's an encrypted entry, we pretend its stored plain text
363         sal_Int16 nTmpFlag = rEntry.nFlag;
364         nTmpFlag &= ~(1 <<4 );
365         aChucker << nTmpFlag;   // general purpose bit flag
366         aChucker << static_cast < sal_Int16 > ( STORED ); // compression method
367     }
368     else
369     {
370         aChucker << rEntry.nFlag; // general purpose bit flag
371         aChucker << rEntry.nMethod; // compression method
372     }
373 
374     aChucker << static_cast < sal_uInt32 > (rEntry.nTime); // last mod file time & date
375     if ((rEntry.nFlag & 8) == 8 )
376     {
377         aChucker << static_cast < sal_Int32 > (0); // crc-32
378         aChucker << static_cast < sal_Int32 > (0); // compressed size
379         aChucker << static_cast < sal_Int32 > (0); // uncompressed size
380     }
381     else
382     {
383         aChucker << static_cast < sal_uInt32 > (rEntry.nCrc); // crc-32
384         aChucker << rEntry.nCompressedSize; // compressed size
385         aChucker << rEntry.nSize; // uncompressed size
386     }
387     aChucker << nNameLength; // file name length
388     aChucker << static_cast < sal_Int16 > (0); // extra field length
389 
390     Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
391     aChucker.WriteBytes( aSequence ); // file name
392 
393     return LOCHDR + nNameLength;
394 }
getCurrentDosTime()395 sal_uInt32 ZipOutputStream::getCurrentDosTime( )
396 {
397     oslDateTime aDateTime;
398     TimeValue aTimeValue;
399     osl_getSystemTime ( &aTimeValue );
400     osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime);
401 
402     sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year);
403 
404     if (nYear>1980)
405         nYear-=1980;
406     else if (nYear>80)
407         nYear-=80;
408     sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) +
409                                           ( 32 * (aDateTime.Month)) +
410                                           ( 512 * nYear ) ) << 16) |
411                                         ( ( aDateTime.Seconds/2) +
412                                           ( 32 * aDateTime.Minutes) +
413                                           ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) );
414     return nResult;
415 }
416 /*
417 
418    This is actually never used, so I removed it, but thought that the
419    implementation details may be useful in the future...mtg 20010307
420 
421    I stopped using the time library and used the OSL version instead, but
422    it might still be useful to have this code here..
423 
424 void ZipOutputStream::dosDateToTMDate ( tm &rTime, sal_uInt32 nDosDate)
425 {
426     sal_uInt32 nDate = static_cast < sal_uInt32 > (nDosDate >> 16);
427     rTime.tm_mday = static_cast < sal_uInt32 > ( nDate & 0x1F);
428     rTime.tm_mon  = static_cast < sal_uInt32 > ( ( ( (nDate) & 0x1E0)/0x20)-1);
429     rTime.tm_year = static_cast < sal_uInt32 > ( ( (nDate & 0x0FE00)/0x0200)+1980);
430 
431     rTime.tm_hour = static_cast < sal_uInt32 > ( (nDosDate & 0xF800)/0x800);
432     rTime.tm_min  = static_cast < sal_uInt32 > ( (nDosDate & 0x7E0)/0x20);
433     rTime.tm_sec  = static_cast < sal_uInt32 > ( 2 * (nDosDate & 0x1F) );
434 }
435 */
436