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