xref: /AOO41X/main/package/source/zipapi/ZipOutputStream.cxx (revision a38728232e8c39f9058a1a2aa8ee4e6db7b8ca34)
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     throw(RuntimeException)
73 {
74     nMethod = static_cast < sal_Int16 > (nNewMethod);
75 }
setLevel(sal_Int32 nNewLevel)76 void SAL_CALL ZipOutputStream::setLevel( sal_Int32 nNewLevel )
77     throw(RuntimeException)
78 {
79     aDeflater.setLevel( nNewLevel);
80 }
81 
putNextEntry(ZipEntry & rEntry,ZipPackageStream * pStream,sal_Bool bEncrypt)82 void SAL_CALL ZipOutputStream::putNextEntry( ZipEntry& rEntry,
83                         ZipPackageStream* pStream,
84                         sal_Bool bEncrypt)
85     throw(IOException, RuntimeException)
86 {
87     if (pCurrentEntry != NULL)
88         closeEntry();
89     if (rEntry.nTime == -1)
90         rEntry.nTime = getCurrentDosTime();
91     if (rEntry.nMethod == -1)
92         rEntry.nMethod = nMethod;
93     rEntry.nVersion = 20;
94     rEntry.nFlag = 1 << 11;
95     if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 ||
96         rEntry.nCrc == -1)
97     {
98         rEntry.nSize = rEntry.nCompressedSize = 0;
99         rEntry.nFlag |= 8;
100     }
101 
102     if (bEncrypt)
103     {
104         bEncryptCurrentEntry = sal_True;
105 
106         m_xCipherContext = ZipFile::StaticGetCipher( m_xFactory, pStream->GetEncryptionData(), true );
107         m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xFactory, pStream->GetEncryptionData() );
108         mnDigested = 0;
109         rEntry.nFlag |= 1 << 4;
110         m_pCurrentStream = pStream;
111     }
112     sal_Int32 nLOCLength = writeLOC(rEntry);
113     rEntry.nOffset = static_cast < sal_Int32 > (aChucker.GetPosition()) - nLOCLength;
114     aZipList.push_back( &rEntry );
115     pCurrentEntry = &rEntry;
116 }
117 
closeEntry()118 void SAL_CALL ZipOutputStream::closeEntry(  )
119     throw(IOException, RuntimeException)
120 {
121     ZipEntry *pEntry = pCurrentEntry;
122     if (pEntry)
123     {
124         switch (pEntry->nMethod)
125         {
126             case DEFLATED:
127                 aDeflater.finish();
128                 while (!aDeflater.finished())
129                     doDeflate();
130                 if ((pEntry->nFlag & 8) == 0)
131                 {
132                     if (pEntry->nSize != aDeflater.getTotalIn())
133                     {
134                         OSL_ENSURE(false,"Invalid entry size");
135                     }
136                     if (pEntry->nCompressedSize != aDeflater.getTotalOut())
137                     {
138                         //VOS_DEBUG_ONLY("Invalid entry compressed size");
139                         // Different compression strategies make the merit of this
140                         // test somewhat dubious
141                         pEntry->nCompressedSize = aDeflater.getTotalOut();
142                     }
143                     if (pEntry->nCrc != aCRC.getValue())
144                     {
145                         OSL_ENSURE(false,"Invalid entry CRC-32");
146                     }
147                 }
148                 else
149                 {
150                     if ( !bEncryptCurrentEntry )
151                     {
152                         pEntry->nSize = aDeflater.getTotalIn();
153                         pEntry->nCompressedSize = aDeflater.getTotalOut();
154                     }
155                     pEntry->nCrc = aCRC.getValue();
156                     writeEXT(*pEntry);
157                 }
158                 aDeflater.reset();
159                 aCRC.reset();
160                 break;
161             case STORED:
162                 if (!((pEntry->nFlag & 8) == 0))
163                     OSL_ENSURE ( false, "Serious error, one of compressed size, size or CRC was -1 in a STORED stream");
164                 break;
165             default:
166                 OSL_ENSURE(false,"Invalid compression method");
167                 break;
168         }
169 
170         if (bEncryptCurrentEntry)
171         {
172             bEncryptCurrentEntry = sal_False;
173 
174             m_xCipherContext.clear();
175 
176             uno::Sequence< sal_Int8 > aDigestSeq;
177             if ( m_xDigestContext.is() )
178             {
179                 aDigestSeq = m_xDigestContext->finalizeDigestAndDispose();
180                 m_xDigestContext.clear();
181             }
182 
183             if ( m_pCurrentStream )
184                 m_pCurrentStream->setDigest( aDigestSeq );
185         }
186         pCurrentEntry = NULL;
187         m_pCurrentStream = NULL;
188     }
189 }
190 
write(const Sequence<sal_Int8> & rBuffer,sal_Int32 nNewOffset,sal_Int32 nNewLength)191 void SAL_CALL ZipOutputStream::write( const Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength )
192     throw(IOException, RuntimeException)
193 {
194     switch (pCurrentEntry->nMethod)
195     {
196         case DEFLATED:
197             if (!aDeflater.finished())
198             {
199                 aDeflater.setInputSegment(rBuffer, nNewOffset, nNewLength);
200                 while (!aDeflater.needsInput())
201                     doDeflate();
202                 if (!bEncryptCurrentEntry)
203                     aCRC.updateSegment(rBuffer, nNewOffset, nNewLength);
204             }
205             break;
206         case STORED:
207             {
208                 Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
209                 aChucker.WriteBytes( aTmpBuffer );
210             }
211             break;
212     }
213 }
214 
rawWrite(Sequence<sal_Int8> & rBuffer,sal_Int32,sal_Int32 nNewLength)215 void SAL_CALL ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength )
216     throw(IOException, RuntimeException)
217 {
218     Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
219     aChucker.WriteBytes( aTmpBuffer );
220 }
221 
rawCloseEntry()222 void SAL_CALL ZipOutputStream::rawCloseEntry(  )
223     throw(IOException, RuntimeException)
224 {
225     if ( pCurrentEntry->nMethod == DEFLATED && ( pCurrentEntry->nFlag & 8 ) )
226         writeEXT(*pCurrentEntry);
227     pCurrentEntry = NULL;
228 }
229 
finish()230 void SAL_CALL ZipOutputStream::finish(  )
231     throw(IOException, RuntimeException)
232 {
233     if (bFinished)
234         return;
235 
236     if (pCurrentEntry != NULL)
237         closeEntry();
238 
239     if (aZipList.size() < 1)
240         OSL_ENSURE(false,"Zip file must have at least one entry!\n");
241 
242     sal_Int32 nOffset= static_cast < sal_Int32 > (aChucker.GetPosition());
243     for (sal_Int32 i =0, nEnd = aZipList.size(); i < nEnd; i++)
244         writeCEN( *aZipList[i] );
245     writeEND( nOffset, static_cast < sal_Int32 > (aChucker.GetPosition()) - nOffset);
246     bFinished = sal_True;
247     xStream->flush();
248 }
249 
doDeflate()250 void ZipOutputStream::doDeflate()
251 {
252     sal_Int32 nLength = aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength());
253 
254     if ( nLength > 0 )
255     {
256         uno::Sequence< sal_Int8 > aTmpBuffer( m_aDeflateBuffer.getConstArray(), nLength );
257         if ( bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
258         {
259             // Need to update our digest before encryption...
260             sal_Int32 nDiff = n_ConstDigestLength - mnDigested;
261             if ( nDiff )
262             {
263                 sal_Int32 nEat = ::std::min( nLength, nDiff );
264                 uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat );
265                 m_xDigestContext->updateDigest( aTmpSeq );
266                 mnDigested = mnDigested + static_cast< sal_Int16 >( nEat );
267             }
268 
269             uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer );
270 
271             aChucker.WriteBytes( aEncryptionBuffer );
272 
273             // the sizes as well as checksum for encrypted streams is calculated here
274             pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
275             pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
276             aCRC.update( aEncryptionBuffer );
277         }
278         else
279         {
280             aChucker.WriteBytes ( aTmpBuffer );
281         }
282     }
283 
284     if ( aDeflater.finished() && bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
285     {
286         uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose();
287         if ( aEncryptionBuffer.getLength() )
288         {
289             aChucker.WriteBytes( aEncryptionBuffer );
290 
291             // the sizes as well as checksum for encrypted streams is calculated hier
292             pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
293             pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
294             aCRC.update( aEncryptionBuffer );
295         }
296     }
297 }
298 
writeEND(sal_uInt32 nOffset,sal_uInt32 nLength)299 void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength)
300     throw(IOException, RuntimeException)
301 {
302     aChucker << ENDSIG;
303     aChucker << static_cast < sal_Int16 > ( 0 );
304     aChucker << static_cast < sal_Int16 > ( 0 );
305     aChucker << static_cast < sal_Int16 > ( aZipList.size() );
306     aChucker << static_cast < sal_Int16 > ( aZipList.size() );
307     aChucker << nLength;
308     aChucker << nOffset;
309     aChucker << static_cast < sal_Int16 > ( 0 );
310 }
writeCEN(const ZipEntry & rEntry)311 void ZipOutputStream::writeCEN( const ZipEntry &rEntry )
312     throw(IOException, RuntimeException)
313 {
314     if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
315         throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() );
316 
317     ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
318     sal_Int16 nNameLength       = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
319 
320     aChucker << CENSIG;
321     aChucker << rEntry.nVersion;
322     aChucker << rEntry.nVersion;
323     if (rEntry.nFlag & (1 << 4) )
324     {
325         // If it's an encrypted entry, we pretend its stored plain text
326         ZipEntry *pEntry = const_cast < ZipEntry * > ( &rEntry );
327         pEntry->nFlag &= ~(1 <<4 );
328         aChucker << rEntry.nFlag;
329         aChucker << static_cast < sal_Int16 > ( STORED );
330     }
331     else
332     {
333         aChucker << rEntry.nFlag;
334         aChucker << rEntry.nMethod;
335     }
336     aChucker << static_cast < sal_uInt32> ( rEntry.nTime );
337     aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
338     aChucker << rEntry.nCompressedSize;
339     aChucker << rEntry.nSize;
340     aChucker << nNameLength;
341     aChucker << static_cast < sal_Int16> (0);
342     aChucker << static_cast < sal_Int16> (0);
343     aChucker << static_cast < sal_Int16> (0);
344     aChucker << static_cast < sal_Int16> (0);
345     aChucker << static_cast < sal_Int32> (0);
346     aChucker << rEntry.nOffset;
347 
348     Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
349     aChucker.WriteBytes( aSequence );
350 }
writeEXT(const ZipEntry & rEntry)351 void ZipOutputStream::writeEXT( const ZipEntry &rEntry )
352     throw(IOException, RuntimeException)
353 {
354     aChucker << EXTSIG;
355     aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
356     aChucker << rEntry.nCompressedSize;
357     aChucker << rEntry.nSize;
358 }
359 
writeLOC(const ZipEntry & rEntry)360 sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry )
361     throw(IOException, RuntimeException)
362 {
363     if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
364         throw IOException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Unexpected character is used in file name." ) ), uno::Reference< XInterface >() );
365 
366     ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
367     sal_Int16 nNameLength       = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
368 
369     aChucker << LOCSIG;
370     aChucker << rEntry.nVersion;
371 
372     if (rEntry.nFlag & (1 << 4) )
373     {
374         // If it's an encrypted entry, we pretend its stored plain text
375         sal_Int16 nTmpFlag = rEntry.nFlag;
376         nTmpFlag &= ~(1 <<4 );
377         aChucker << nTmpFlag;
378         aChucker << static_cast < sal_Int16 > ( STORED );
379     }
380     else
381     {
382         aChucker << rEntry.nFlag;
383         aChucker << rEntry.nMethod;
384     }
385 
386     aChucker << static_cast < sal_uInt32 > (rEntry.nTime);
387     if ((rEntry.nFlag & 8) == 8 )
388     {
389         aChucker << static_cast < sal_Int32 > (0);
390         aChucker << static_cast < sal_Int32 > (0);
391         aChucker << static_cast < sal_Int32 > (0);
392     }
393     else
394     {
395         aChucker << static_cast < sal_uInt32 > (rEntry.nCrc);
396         aChucker << rEntry.nCompressedSize;
397         aChucker << rEntry.nSize;
398     }
399     aChucker << nNameLength;
400     aChucker << static_cast < sal_Int16 > (0);
401 
402     Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
403     aChucker.WriteBytes( aSequence );
404 
405     return LOCHDR + nNameLength;
406 }
getCurrentDosTime()407 sal_uInt32 ZipOutputStream::getCurrentDosTime( )
408 {
409     oslDateTime aDateTime;
410     TimeValue aTimeValue;
411     osl_getSystemTime ( &aTimeValue );
412     osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime);
413 
414     sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year);
415 
416     if (nYear>1980)
417         nYear-=1980;
418     else if (nYear>80)
419         nYear-=80;
420     sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) +
421                                           ( 32 * (aDateTime.Month)) +
422                                           ( 512 * nYear ) ) << 16) |
423                                         ( ( aDateTime.Seconds/2) +
424                                           ( 32 * aDateTime.Minutes) +
425                                           ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) );
426     return nResult;
427 }
428 /*
429 
430    This is actually never used, so I removed it, but thought that the
431    implementation details may be useful in the future...mtg 20010307
432 
433    I stopped using the time library and used the OSL version instead, but
434    it might still be useful to have this code here..
435 
436 void ZipOutputStream::dosDateToTMDate ( tm &rTime, sal_uInt32 nDosDate)
437 {
438     sal_uInt32 nDate = static_cast < sal_uInt32 > (nDosDate >> 16);
439     rTime.tm_mday = static_cast < sal_uInt32 > ( nDate & 0x1F);
440     rTime.tm_mon  = static_cast < sal_uInt32 > ( ( ( (nDate) & 0x1E0)/0x20)-1);
441     rTime.tm_year = static_cast < sal_uInt32 > ( ( (nDate & 0x0FE00)/0x0200)+1980);
442 
443     rTime.tm_hour = static_cast < sal_uInt32 > ( (nDosDate & 0xF800)/0x800);
444     rTime.tm_min  = static_cast < sal_uInt32 > ( (nDosDate & 0x7E0)/0x20);
445     rTime.tm_sec  = static_cast < sal_uInt32 > ( 2 * (nDosDate & 0x1F) );
446 }
447 */
448 
449