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