Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 :
21 : #include <com/sun/star/packages/zip/ZipConstants.hpp>
22 : #include <com/sun/star/io/XOutputStream.hpp>
23 : #include <comphelper/storagehelper.hxx>
24 :
25 : #include <osl/time.h>
26 :
27 : #include <EncryptionData.hxx>
28 : #include <PackageConstants.hxx>
29 : #include <ZipEntry.hxx>
30 : #include <ZipFile.hxx>
31 : #include <ZipPackageStream.hxx>
32 : #include <ZipOutputStream.hxx>
33 :
34 : using namespace com::sun::star;
35 : using namespace com::sun::star::io;
36 : using namespace com::sun::star::uno;
37 : using namespace com::sun::star::packages;
38 : using namespace com::sun::star::packages::zip;
39 : using namespace com::sun::star::packages::zip::ZipConstants;
40 :
41 : /** This class is used to write Zip files
42 : */
43 33 : ZipOutputStream::ZipOutputStream( const uno::Reference< uno::XComponentContext >& rxContext,
44 : const uno::Reference < XOutputStream > &xOStream )
45 : : m_xContext( rxContext )
46 : , xStream(xOStream)
47 : , m_aDeflateBuffer(n_ConstBufferSize)
48 : , aDeflater(DEFAULT_COMPRESSION, sal_True)
49 : , aChucker(xOStream)
50 : , pCurrentEntry(NULL)
51 : , nMethod(DEFLATED)
52 : , bFinished(sal_False)
53 : , bEncryptCurrentEntry(sal_False)
54 33 : , m_pCurrentStream(NULL)
55 : {
56 33 : }
57 :
58 66 : ZipOutputStream::~ZipOutputStream( void )
59 : {
60 333 : for (sal_Int32 i = 0, nEnd = aZipList.size(); i < nEnd; i++)
61 300 : delete aZipList[i];
62 33 : }
63 :
64 33 : void SAL_CALL ZipOutputStream::setMethod( sal_Int32 nNewMethod )
65 : throw(RuntimeException)
66 : {
67 33 : nMethod = static_cast < sal_Int16 > (nNewMethod);
68 33 : }
69 33 : void SAL_CALL ZipOutputStream::setLevel( sal_Int32 nNewLevel )
70 : throw(RuntimeException)
71 : {
72 33 : aDeflater.setLevel( nNewLevel);
73 33 : }
74 :
75 300 : void SAL_CALL ZipOutputStream::putNextEntry( ZipEntry& rEntry,
76 : ZipPackageStream* pStream,
77 : sal_Bool bEncrypt)
78 : throw(IOException, RuntimeException)
79 : {
80 300 : if (pCurrentEntry != NULL)
81 0 : closeEntry();
82 300 : if (rEntry.nTime == -1)
83 259 : rEntry.nTime = getCurrentDosTime();
84 300 : if (rEntry.nMethod == -1)
85 0 : rEntry.nMethod = nMethod;
86 300 : rEntry.nVersion = 20;
87 300 : rEntry.nFlag = 1 << 11;
88 300 : if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 ||
89 : rEntry.nCrc == -1)
90 : {
91 263 : rEntry.nSize = rEntry.nCompressedSize = 0;
92 263 : rEntry.nFlag |= 8;
93 : }
94 :
95 300 : if (bEncrypt)
96 : {
97 4 : bEncryptCurrentEntry = sal_True;
98 :
99 4 : m_xCipherContext = ZipFile::StaticGetCipher( m_xContext, pStream->GetEncryptionData(), true );
100 4 : m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xContext, pStream->GetEncryptionData() );
101 4 : mnDigested = 0;
102 4 : rEntry.nFlag |= 1 << 4;
103 4 : m_pCurrentStream = pStream;
104 : }
105 300 : sal_Int32 nLOCLength = writeLOC(rEntry);
106 300 : rEntry.nOffset = aChucker.GetPosition() - nLOCLength;
107 300 : aZipList.push_back( &rEntry );
108 300 : pCurrentEntry = &rEntry;
109 300 : }
110 :
111 276 : void SAL_CALL ZipOutputStream::closeEntry( )
112 : throw(IOException, RuntimeException)
113 : {
114 276 : ZipEntry *pEntry = pCurrentEntry;
115 276 : if (pEntry)
116 : {
117 276 : switch (pEntry->nMethod)
118 : {
119 : case DEFLATED:
120 263 : aDeflater.finish();
121 789 : while (!aDeflater.finished())
122 263 : doDeflate();
123 263 : if ((pEntry->nFlag & 8) == 0)
124 : {
125 0 : if (pEntry->nSize != aDeflater.getTotalIn())
126 : {
127 : OSL_FAIL("Invalid entry size");
128 : }
129 0 : if (pEntry->nCompressedSize != aDeflater.getTotalOut())
130 : {
131 : // Different compression strategies make the merit of this
132 : // test somewhat dubious
133 0 : pEntry->nCompressedSize = aDeflater.getTotalOut();
134 : }
135 0 : if (pEntry->nCrc != aCRC.getValue())
136 : {
137 : OSL_FAIL("Invalid entry CRC-32");
138 : }
139 : }
140 : else
141 : {
142 263 : if ( !bEncryptCurrentEntry )
143 : {
144 259 : pEntry->nSize = aDeflater.getTotalIn();
145 259 : pEntry->nCompressedSize = aDeflater.getTotalOut();
146 : }
147 263 : pEntry->nCrc = aCRC.getValue();
148 263 : writeEXT(*pEntry);
149 : }
150 263 : aDeflater.reset();
151 263 : aCRC.reset();
152 263 : break;
153 : case STORED:
154 13 : if (!((pEntry->nFlag & 8) == 0))
155 : OSL_FAIL( "Serious error, one of compressed size, size or CRC was -1 in a STORED stream");
156 13 : break;
157 : default:
158 : OSL_FAIL("Invalid compression method");
159 0 : break;
160 : }
161 :
162 276 : if (bEncryptCurrentEntry)
163 : {
164 4 : bEncryptCurrentEntry = sal_False;
165 :
166 4 : m_xCipherContext.clear();
167 :
168 4 : uno::Sequence< sal_Int8 > aDigestSeq;
169 4 : if ( m_xDigestContext.is() )
170 : {
171 4 : aDigestSeq = m_xDigestContext->finalizeDigestAndDispose();
172 4 : m_xDigestContext.clear();
173 : }
174 :
175 4 : if ( m_pCurrentStream )
176 4 : m_pCurrentStream->setDigest( aDigestSeq );
177 : }
178 276 : pCurrentEntry = NULL;
179 276 : m_pCurrentStream = NULL;
180 : }
181 276 : }
182 :
183 280 : void SAL_CALL ZipOutputStream::write( const Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength )
184 : throw(IOException, RuntimeException)
185 : {
186 280 : switch (pCurrentEntry->nMethod)
187 : {
188 : case DEFLATED:
189 267 : if (!aDeflater.finished())
190 : {
191 267 : aDeflater.setInputSegment(rBuffer, nNewOffset, nNewLength);
192 798 : while (!aDeflater.needsInput())
193 264 : doDeflate();
194 267 : if (!bEncryptCurrentEntry)
195 263 : aCRC.updateSegment(rBuffer, nNewOffset, nNewLength);
196 : }
197 267 : break;
198 : case STORED:
199 : {
200 13 : Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
201 13 : aChucker.WriteBytes( aTmpBuffer );
202 : }
203 13 : break;
204 : }
205 280 : }
206 :
207 0 : void SAL_CALL ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength )
208 : throw(IOException, RuntimeException)
209 : {
210 0 : Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
211 0 : aChucker.WriteBytes( aTmpBuffer );
212 0 : }
213 :
214 24 : void SAL_CALL ZipOutputStream::rawCloseEntry( )
215 : throw(IOException, RuntimeException)
216 : {
217 24 : if ( pCurrentEntry->nMethod == DEFLATED && ( pCurrentEntry->nFlag & 8 ) )
218 0 : writeEXT(*pCurrentEntry);
219 24 : pCurrentEntry = NULL;
220 24 : }
221 :
222 33 : void SAL_CALL ZipOutputStream::finish( )
223 : throw(IOException, RuntimeException)
224 : {
225 33 : if (bFinished)
226 33 : return;
227 :
228 33 : if (pCurrentEntry != NULL)
229 0 : closeEntry();
230 :
231 33 : if (aZipList.size() < 1)
232 : OSL_FAIL("Zip file must have at least one entry!\n");
233 :
234 33 : sal_Int32 nOffset= static_cast < sal_Int32 > (aChucker.GetPosition());
235 333 : for (sal_Int32 i =0, nEnd = aZipList.size(); i < nEnd; i++)
236 300 : writeCEN( *aZipList[i] );
237 33 : writeEND( nOffset, static_cast < sal_Int32 > (aChucker.GetPosition()) - nOffset);
238 33 : bFinished = sal_True;
239 33 : xStream->flush();
240 : }
241 :
242 527 : void ZipOutputStream::doDeflate()
243 : {
244 527 : sal_Int32 nLength = aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength());
245 :
246 527 : if ( nLength > 0 )
247 : {
248 264 : uno::Sequence< sal_Int8 > aTmpBuffer( m_aDeflateBuffer.getConstArray(), nLength );
249 264 : if ( bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
250 : {
251 : // Need to update our digest before encryption...
252 4 : sal_Int32 nDiff = n_ConstDigestLength - mnDigested;
253 4 : if ( nDiff )
254 : {
255 4 : sal_Int32 nEat = ::std::min( nLength, nDiff );
256 4 : uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat );
257 4 : m_xDigestContext->updateDigest( aTmpSeq );
258 4 : mnDigested = mnDigested + static_cast< sal_Int16 >( nEat );
259 : }
260 :
261 : // FIXME64: uno::Sequence not 64bit safe.
262 4 : uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer );
263 :
264 4 : aChucker.WriteBytes( aEncryptionBuffer );
265 :
266 : // the sizes as well as checksum for encrypted streams is calculated here
267 4 : pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
268 4 : pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
269 4 : aCRC.update( aEncryptionBuffer );
270 : }
271 : else
272 : {
273 260 : aChucker.WriteBytes ( aTmpBuffer );
274 264 : }
275 : }
276 :
277 527 : if ( aDeflater.finished() && bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
278 : {
279 : // FIXME64: sequence not 64bit safe.
280 4 : uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose();
281 4 : if ( aEncryptionBuffer.getLength() )
282 : {
283 4 : aChucker.WriteBytes( aEncryptionBuffer );
284 :
285 : // the sizes as well as checksum for encrypted streams is calculated hier
286 4 : pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
287 4 : pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
288 4 : aCRC.update( aEncryptionBuffer );
289 4 : }
290 : }
291 527 : }
292 :
293 33 : void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength)
294 : throw(IOException, RuntimeException)
295 : {
296 33 : aChucker << ENDSIG;
297 33 : aChucker << static_cast < sal_Int16 > ( 0 );
298 33 : aChucker << static_cast < sal_Int16 > ( 0 );
299 33 : aChucker << static_cast < sal_Int16 > ( aZipList.size() );
300 33 : aChucker << static_cast < sal_Int16 > ( aZipList.size() );
301 33 : aChucker << nLength;
302 33 : aChucker << nOffset;
303 33 : aChucker << static_cast < sal_Int16 > ( 0 );
304 33 : }
305 :
306 1500 : static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated )
307 : {
308 1500 : if( nNum >= 0xffffffff )
309 : {
310 0 : *pIsTruncated = true;
311 0 : return 0xffffffff;
312 : }
313 : else
314 1500 : return static_cast< sal_uInt32 >( nNum );
315 : }
316 :
317 300 : void ZipOutputStream::writeCEN( const ZipEntry &rEntry )
318 : throw(IOException, RuntimeException)
319 : {
320 300 : if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
321 0 : throw IOException("Unexpected character is used in file name.", uno::Reference< XInterface >() );
322 :
323 300 : ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
324 300 : sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
325 :
326 300 : aChucker << CENSIG;
327 300 : aChucker << rEntry.nVersion;
328 300 : aChucker << rEntry.nVersion;
329 300 : if (rEntry.nFlag & (1 << 4) )
330 : {
331 : // If it's an encrypted entry, we pretend its stored plain text
332 4 : ZipEntry *pEntry = const_cast < ZipEntry * > ( &rEntry );
333 4 : pEntry->nFlag &= ~(1 <<4 );
334 4 : aChucker << rEntry.nFlag;
335 4 : aChucker << static_cast < sal_Int16 > ( STORED );
336 : }
337 : else
338 : {
339 296 : aChucker << rEntry.nFlag;
340 296 : aChucker << rEntry.nMethod;
341 : }
342 300 : bool bWrite64Header = false;
343 :
344 300 : aChucker << static_cast < sal_uInt32> ( rEntry.nTime );
345 300 : aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
346 300 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
347 300 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
348 300 : aChucker << nNameLength;
349 300 : aChucker << static_cast < sal_Int16> (0);
350 300 : aChucker << static_cast < sal_Int16> (0);
351 300 : aChucker << static_cast < sal_Int16> (0);
352 300 : aChucker << static_cast < sal_Int16> (0);
353 300 : aChucker << static_cast < sal_Int32> (0);
354 300 : aChucker << getTruncated( rEntry.nOffset, &bWrite64Header );
355 :
356 300 : if( bWrite64Header )
357 : {
358 : // FIXME64: need to append a ZIP64 header instead of throwing
359 : // We're about to silently loose people's data - which they are
360 : // unlikely to appreciate so fail instead:
361 : throw IOException( "File contains streams that are too large.",
362 0 : uno::Reference< XInterface >() );
363 : }
364 :
365 300 : Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
366 300 : aChucker.WriteBytes( aSequence );
367 300 : }
368 263 : void ZipOutputStream::writeEXT( const ZipEntry &rEntry )
369 : throw(IOException, RuntimeException)
370 : {
371 263 : bool bWrite64Header = false;
372 :
373 263 : aChucker << EXTSIG;
374 263 : aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
375 263 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
376 263 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
377 :
378 263 : if( bWrite64Header )
379 : {
380 : // FIXME64: need to append a ZIP64 header instead of throwing
381 : // We're about to silently loose people's data - which they are
382 : // unlikely to appreciate so fail instead:
383 : throw IOException( "File contains streams that are too large.",
384 0 : uno::Reference< XInterface >() );
385 : }
386 263 : }
387 :
388 300 : sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry )
389 : throw(IOException, RuntimeException)
390 : {
391 300 : if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
392 0 : throw IOException("Unexpected character is used in file name.", uno::Reference< XInterface >() );
393 :
394 300 : ::rtl::OString sUTF8Name = ::rtl::OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
395 300 : sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
396 :
397 300 : aChucker << LOCSIG;
398 300 : aChucker << rEntry.nVersion;
399 :
400 300 : if (rEntry.nFlag & (1 << 4) )
401 : {
402 : // If it's an encrypted entry, we pretend its stored plain text
403 4 : sal_Int16 nTmpFlag = rEntry.nFlag;
404 4 : nTmpFlag &= ~(1 <<4 );
405 4 : aChucker << nTmpFlag;
406 4 : aChucker << static_cast < sal_Int16 > ( STORED );
407 : }
408 : else
409 : {
410 296 : aChucker << rEntry.nFlag;
411 296 : aChucker << rEntry.nMethod;
412 : }
413 :
414 300 : bool bWrite64Header = false;
415 :
416 300 : aChucker << static_cast < sal_uInt32 > (rEntry.nTime);
417 300 : if ((rEntry.nFlag & 8) == 8 )
418 : {
419 263 : aChucker << static_cast < sal_Int32 > (0);
420 263 : aChucker << static_cast < sal_Int32 > (0);
421 263 : aChucker << static_cast < sal_Int32 > (0);
422 : }
423 : else
424 : {
425 37 : aChucker << static_cast < sal_uInt32 > (rEntry.nCrc);
426 37 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
427 37 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
428 : }
429 300 : aChucker << nNameLength;
430 300 : aChucker << static_cast < sal_Int16 > (0);
431 :
432 300 : if( bWrite64Header )
433 : {
434 : // FIXME64: need to append a ZIP64 header instead of throwing
435 : // We're about to silently loose people's data - which they are
436 : // unlikely to appreciate so fail instead:
437 : throw IOException( "File contains streams that are too large.",
438 0 : uno::Reference< XInterface >() );
439 : }
440 :
441 300 : Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
442 300 : aChucker.WriteBytes( aSequence );
443 :
444 300 : return LOCHDR + nNameLength;
445 : }
446 300 : sal_uInt32 ZipOutputStream::getCurrentDosTime( )
447 : {
448 : oslDateTime aDateTime;
449 : TimeValue aTimeValue;
450 300 : osl_getSystemTime ( &aTimeValue );
451 300 : osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime);
452 :
453 300 : sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year);
454 :
455 300 : if (nYear>1980)
456 300 : nYear-=1980;
457 0 : else if (nYear>80)
458 0 : nYear-=80;
459 : sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) +
460 : ( 32 * (aDateTime.Month)) +
461 : ( 512 * nYear ) ) << 16) |
462 : ( ( aDateTime.Seconds/2) +
463 : ( 32 * aDateTime.Minutes) +
464 300 : ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) );
465 300 : return nResult;
466 : }
467 : /*
468 :
469 : This is actually never used, so I removed it, but thought that the
470 : implementation details may be useful in the future...mtg 20010307
471 :
472 : I stopped using the time library and used the OSL version instead, but
473 : it might still be useful to have this code here..
474 :
475 : void ZipOutputStream::dosDateToTMDate ( tm &rTime, sal_uInt32 nDosDate)
476 : {
477 : sal_uInt32 nDate = static_cast < sal_uInt32 > (nDosDate >> 16);
478 : rTime.tm_mday = static_cast < sal_uInt32 > ( nDate & 0x1F);
479 : rTime.tm_mon = static_cast < sal_uInt32 > ( ( ( (nDate) & 0x1E0)/0x20)-1);
480 : rTime.tm_year = static_cast < sal_uInt32 > ( ( (nDate & 0x0FE00)/0x0200)+1980);
481 :
482 : rTime.tm_hour = static_cast < sal_uInt32 > ( (nDosDate & 0xF800)/0x800);
483 : rTime.tm_min = static_cast < sal_uInt32 > ( (nDosDate & 0x7E0)/0x20);
484 : rTime.tm_sec = static_cast < sal_uInt32 > ( 2 * (nDosDate & 0x1F) );
485 : }
486 : */
487 :
488 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|