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 229 : 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 229 : , m_pCurrentStream(NULL)
55 : {
56 229 : }
57 :
58 458 : ZipOutputStream::~ZipOutputStream( void )
59 : {
60 1728 : for (sal_Int32 i = 0, nEnd = aZipList.size(); i < nEnd; i++)
61 1499 : delete aZipList[i];
62 229 : }
63 :
64 229 : void SAL_CALL ZipOutputStream::setMethod( sal_Int32 nNewMethod )
65 : throw(RuntimeException)
66 : {
67 229 : nMethod = static_cast < sal_Int16 > (nNewMethod);
68 229 : }
69 229 : void SAL_CALL ZipOutputStream::setLevel( sal_Int32 nNewLevel )
70 : throw(RuntimeException)
71 : {
72 229 : aDeflater.setLevel( nNewLevel);
73 229 : }
74 :
75 1499 : void SAL_CALL ZipOutputStream::putNextEntry( ZipEntry& rEntry,
76 : ZipPackageStream* pStream,
77 : sal_Bool bEncrypt)
78 : throw(IOException, RuntimeException)
79 : {
80 1499 : if (pCurrentEntry != NULL)
81 0 : closeEntry();
82 1499 : if (rEntry.nTime == -1)
83 1048 : rEntry.nTime = getCurrentDosTime();
84 1499 : if (rEntry.nMethod == -1)
85 0 : rEntry.nMethod = nMethod;
86 1499 : rEntry.nVersion = 20;
87 1499 : rEntry.nFlag = 1 << 11;
88 1897 : if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 ||
89 398 : rEntry.nCrc == -1)
90 : {
91 1101 : rEntry.nSize = rEntry.nCompressedSize = 0;
92 1101 : rEntry.nFlag |= 8;
93 : }
94 :
95 1499 : if (bEncrypt)
96 : {
97 5 : bEncryptCurrentEntry = sal_True;
98 :
99 5 : m_xCipherContext = ZipFile::StaticGetCipher( m_xContext, pStream->GetEncryptionData(), true );
100 5 : m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( m_xContext, pStream->GetEncryptionData() );
101 5 : mnDigested = 0;
102 5 : rEntry.nFlag |= 1 << 4;
103 5 : m_pCurrentStream = pStream;
104 : }
105 1499 : sal_Int32 nLOCLength = writeLOC(rEntry);
106 1499 : rEntry.nOffset = aChucker.GetPosition() - nLOCLength;
107 1499 : aZipList.push_back( &rEntry );
108 1499 : pCurrentEntry = &rEntry;
109 1499 : }
110 :
111 1306 : void SAL_CALL ZipOutputStream::closeEntry( )
112 : throw(IOException, RuntimeException)
113 : {
114 1306 : ZipEntry *pEntry = pCurrentEntry;
115 1306 : if (pEntry)
116 : {
117 1306 : switch (pEntry->nMethod)
118 : {
119 : case DEFLATED:
120 1101 : aDeflater.finish();
121 3303 : while (!aDeflater.finished())
122 1101 : doDeflate();
123 1101 : 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 1101 : if ( !bEncryptCurrentEntry )
143 : {
144 1096 : pEntry->nSize = aDeflater.getTotalIn();
145 1096 : pEntry->nCompressedSize = aDeflater.getTotalOut();
146 : }
147 1101 : pEntry->nCrc = aCRC.getValue();
148 1101 : writeEXT(*pEntry);
149 : }
150 1101 : aDeflater.reset();
151 1101 : aCRC.reset();
152 1101 : break;
153 : case STORED:
154 205 : if (!((pEntry->nFlag & 8) == 0))
155 : OSL_FAIL( "Serious error, one of compressed size, size or CRC was -1 in a STORED stream");
156 205 : break;
157 : default:
158 : OSL_FAIL("Invalid compression method");
159 0 : break;
160 : }
161 :
162 1306 : if (bEncryptCurrentEntry)
163 : {
164 5 : bEncryptCurrentEntry = sal_False;
165 :
166 5 : m_xCipherContext.clear();
167 :
168 5 : uno::Sequence< sal_Int8 > aDigestSeq;
169 5 : if ( m_xDigestContext.is() )
170 : {
171 5 : aDigestSeq = m_xDigestContext->finalizeDigestAndDispose();
172 5 : m_xDigestContext.clear();
173 : }
174 :
175 5 : if ( m_pCurrentStream )
176 5 : m_pCurrentStream->setDigest( aDigestSeq );
177 : }
178 1306 : pCurrentEntry = NULL;
179 1306 : m_pCurrentStream = NULL;
180 : }
181 1306 : }
182 :
183 1343 : void SAL_CALL ZipOutputStream::write( const Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength )
184 : throw(IOException, RuntimeException)
185 : {
186 1343 : switch (pCurrentEntry->nMethod)
187 : {
188 : case DEFLATED:
189 1138 : if (!aDeflater.finished())
190 : {
191 1138 : aDeflater.setInputSegment(rBuffer, nNewOffset, nNewLength);
192 3387 : while (!aDeflater.needsInput())
193 1111 : doDeflate();
194 1138 : if (!bEncryptCurrentEntry)
195 1133 : aCRC.updateSegment(rBuffer, nNewOffset, nNewLength);
196 : }
197 1138 : break;
198 : case STORED:
199 : {
200 205 : Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
201 205 : aChucker.WriteBytes( aTmpBuffer );
202 : }
203 205 : break;
204 : }
205 1343 : }
206 :
207 47 : void SAL_CALL ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength )
208 : throw(IOException, RuntimeException)
209 : {
210 47 : Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength );
211 47 : aChucker.WriteBytes( aTmpBuffer );
212 47 : }
213 :
214 193 : void SAL_CALL ZipOutputStream::rawCloseEntry( )
215 : throw(IOException, RuntimeException)
216 : {
217 193 : if ( pCurrentEntry->nMethod == DEFLATED && ( pCurrentEntry->nFlag & 8 ) )
218 0 : writeEXT(*pCurrentEntry);
219 193 : pCurrentEntry = NULL;
220 193 : }
221 :
222 229 : void SAL_CALL ZipOutputStream::finish( )
223 : throw(IOException, RuntimeException)
224 : {
225 229 : if (bFinished)
226 229 : return;
227 :
228 229 : if (pCurrentEntry != NULL)
229 0 : closeEntry();
230 :
231 229 : if (aZipList.size() < 1)
232 : OSL_FAIL("Zip file must have at least one entry!\n");
233 :
234 229 : sal_Int32 nOffset= static_cast < sal_Int32 > (aChucker.GetPosition());
235 1728 : for (sal_Int32 i =0, nEnd = aZipList.size(); i < nEnd; i++)
236 1499 : writeCEN( *aZipList[i] );
237 229 : writeEND( nOffset, static_cast < sal_Int32 > (aChucker.GetPosition()) - nOffset);
238 229 : bFinished = sal_True;
239 229 : xStream->flush();
240 : }
241 :
242 2212 : void ZipOutputStream::doDeflate()
243 : {
244 2212 : sal_Int32 nLength = aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength());
245 :
246 2212 : if ( nLength > 0 )
247 : {
248 1104 : uno::Sequence< sal_Int8 > aTmpBuffer( m_aDeflateBuffer.getConstArray(), nLength );
249 1104 : if ( bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
250 : {
251 : // Need to update our digest before encryption...
252 5 : sal_Int32 nDiff = n_ConstDigestLength - mnDigested;
253 5 : if ( nDiff )
254 : {
255 5 : sal_Int32 nEat = ::std::min( nLength, nDiff );
256 5 : uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat );
257 5 : m_xDigestContext->updateDigest( aTmpSeq );
258 5 : mnDigested = mnDigested + static_cast< sal_Int16 >( nEat );
259 : }
260 :
261 : // FIXME64: uno::Sequence not 64bit safe.
262 5 : uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer );
263 :
264 5 : aChucker.WriteBytes( aEncryptionBuffer );
265 :
266 : // the sizes as well as checksum for encrypted streams is calculated here
267 5 : pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
268 5 : pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
269 5 : aCRC.update( aEncryptionBuffer );
270 : }
271 : else
272 : {
273 1099 : aChucker.WriteBytes ( aTmpBuffer );
274 1104 : }
275 : }
276 :
277 2212 : if ( aDeflater.finished() && bEncryptCurrentEntry && m_xDigestContext.is() && m_xCipherContext.is() )
278 : {
279 : // FIXME64: sequence not 64bit safe.
280 5 : uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose();
281 5 : if ( aEncryptionBuffer.getLength() )
282 : {
283 5 : aChucker.WriteBytes( aEncryptionBuffer );
284 :
285 : // the sizes as well as checksum for encrypted streams is calculated hier
286 5 : pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength();
287 5 : pCurrentEntry->nSize = pCurrentEntry->nCompressedSize;
288 5 : aCRC.update( aEncryptionBuffer );
289 5 : }
290 : }
291 2212 : }
292 :
293 229 : void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength)
294 : throw(IOException, RuntimeException)
295 : {
296 229 : aChucker << ENDSIG;
297 229 : aChucker << static_cast < sal_Int16 > ( 0 );
298 229 : aChucker << static_cast < sal_Int16 > ( 0 );
299 229 : aChucker << static_cast < sal_Int16 > ( aZipList.size() );
300 229 : aChucker << static_cast < sal_Int16 > ( aZipList.size() );
301 229 : aChucker << nLength;
302 229 : aChucker << nOffset;
303 229 : aChucker << static_cast < sal_Int16 > ( 0 );
304 229 : }
305 :
306 7495 : static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated )
307 : {
308 7495 : if( nNum >= 0xffffffff )
309 : {
310 0 : *pIsTruncated = true;
311 0 : return 0xffffffff;
312 : }
313 : else
314 7495 : return static_cast< sal_uInt32 >( nNum );
315 : }
316 :
317 1499 : void ZipOutputStream::writeCEN( const ZipEntry &rEntry )
318 : throw(IOException, RuntimeException)
319 : {
320 1499 : if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
321 0 : throw IOException("Unexpected character is used in file name.", uno::Reference< XInterface >() );
322 :
323 1499 : OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
324 1499 : sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
325 :
326 1499 : aChucker << CENSIG;
327 1499 : aChucker << rEntry.nVersion;
328 1499 : aChucker << rEntry.nVersion;
329 1499 : if (rEntry.nFlag & (1 << 4) )
330 : {
331 : // If it's an encrypted entry, we pretend its stored plain text
332 5 : ZipEntry *pEntry = const_cast < ZipEntry * > ( &rEntry );
333 5 : pEntry->nFlag &= ~(1 <<4 );
334 5 : aChucker << rEntry.nFlag;
335 5 : aChucker << static_cast < sal_Int16 > ( STORED );
336 : }
337 : else
338 : {
339 1494 : aChucker << rEntry.nFlag;
340 1494 : aChucker << rEntry.nMethod;
341 : }
342 1499 : bool bWrite64Header = false;
343 :
344 1499 : aChucker << static_cast < sal_uInt32> ( rEntry.nTime );
345 1499 : aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
346 1499 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
347 1499 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
348 1499 : aChucker << nNameLength;
349 1499 : aChucker << static_cast < sal_Int16> (0);
350 1499 : aChucker << static_cast < sal_Int16> (0);
351 1499 : aChucker << static_cast < sal_Int16> (0);
352 1499 : aChucker << static_cast < sal_Int16> (0);
353 1499 : aChucker << static_cast < sal_Int32> (0);
354 1499 : aChucker << getTruncated( rEntry.nOffset, &bWrite64Header );
355 :
356 1499 : 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 2998 : Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
366 2998 : aChucker.WriteBytes( aSequence );
367 1499 : }
368 1101 : void ZipOutputStream::writeEXT( const ZipEntry &rEntry )
369 : throw(IOException, RuntimeException)
370 : {
371 1101 : bool bWrite64Header = false;
372 :
373 1101 : aChucker << EXTSIG;
374 1101 : aChucker << static_cast < sal_uInt32> ( rEntry.nCrc );
375 1101 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
376 1101 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
377 :
378 1101 : 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 1101 : }
387 :
388 1499 : sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry )
389 : throw(IOException, RuntimeException)
390 : {
391 1499 : if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, sal_True ) )
392 0 : throw IOException("Unexpected character is used in file name.", uno::Reference< XInterface >() );
393 :
394 1499 : OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
395 1499 : sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
396 :
397 1499 : aChucker << LOCSIG;
398 1499 : aChucker << rEntry.nVersion;
399 :
400 1499 : if (rEntry.nFlag & (1 << 4) )
401 : {
402 : // If it's an encrypted entry, we pretend its stored plain text
403 5 : sal_Int16 nTmpFlag = rEntry.nFlag;
404 5 : nTmpFlag &= ~(1 <<4 );
405 5 : aChucker << nTmpFlag;
406 5 : aChucker << static_cast < sal_Int16 > ( STORED );
407 : }
408 : else
409 : {
410 1494 : aChucker << rEntry.nFlag;
411 1494 : aChucker << rEntry.nMethod;
412 : }
413 :
414 1499 : bool bWrite64Header = false;
415 :
416 1499 : aChucker << static_cast < sal_uInt32 > (rEntry.nTime);
417 1499 : if ((rEntry.nFlag & 8) == 8 )
418 : {
419 1101 : aChucker << static_cast < sal_Int32 > (0);
420 1101 : aChucker << static_cast < sal_Int32 > (0);
421 1101 : aChucker << static_cast < sal_Int32 > (0);
422 : }
423 : else
424 : {
425 398 : aChucker << static_cast < sal_uInt32 > (rEntry.nCrc);
426 398 : aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header );
427 398 : aChucker << getTruncated( rEntry.nSize, &bWrite64Header );
428 : }
429 1499 : aChucker << nNameLength;
430 1499 : aChucker << static_cast < sal_Int16 > (0);
431 :
432 1499 : 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 2998 : Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() );
442 1499 : aChucker.WriteBytes( aSequence );
443 :
444 2998 : return LOCHDR + nNameLength;
445 : }
446 1452 : sal_uInt32 ZipOutputStream::getCurrentDosTime( )
447 : {
448 : oslDateTime aDateTime;
449 : TimeValue aTimeValue;
450 1452 : osl_getSystemTime ( &aTimeValue );
451 1452 : osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime);
452 :
453 : // at year 2108, there is an overflow
454 : // -> some decision needs to be made
455 : // how to handle the ZIP file format (just overflow?)
456 :
457 : // if the current system time is before 1980,
458 : // then the time traveller will have to make a decision
459 : // how to handle the ZIP file format before it is invented
460 : // (just underflow?)
461 :
462 : assert(aDateTime.Year > 1980 && aDateTime.Year < 2108);
463 :
464 1452 : sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year);
465 :
466 1452 : if (nYear>=1980)
467 1452 : nYear-=1980;
468 0 : else if (nYear>=80)
469 : {
470 0 : nYear-=80;
471 : }
472 : sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) +
473 2904 : ( 32 * (aDateTime.Month)) +
474 2904 : ( 512 * nYear ) ) << 16) |
475 : ( ( aDateTime.Seconds/2) +
476 2904 : ( 32 * aDateTime.Minutes) +
477 2904 : ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) );
478 1452 : return nResult;
479 : }
480 : /*
481 :
482 : This is actually never used, so I removed it, but thought that the
483 : implementation details may be useful in the future...mtg 20010307
484 :
485 : I stopped using the time library and used the OSL version instead, but
486 : it might still be useful to have this code here..
487 :
488 : void ZipOutputStream::dosDateToTMDate ( tm &rTime, sal_uInt32 nDosDate)
489 : {
490 : sal_uInt32 nDate = static_cast < sal_uInt32 > (nDosDate >> 16);
491 : rTime.tm_mday = static_cast < sal_uInt32 > ( nDate & 0x1F);
492 : rTime.tm_mon = static_cast < sal_uInt32 > ( ( ( (nDate) & 0x1E0)/0x20)-1);
493 : rTime.tm_year = static_cast < sal_uInt32 > ( ( (nDate & 0x0FE00)/0x0200)+1980);
494 :
495 : rTime.tm_hour = static_cast < sal_uInt32 > ( (nDosDate & 0xF800)/0x800);
496 : rTime.tm_min = static_cast < sal_uInt32 > ( (nDosDate & 0x7E0)/0x20);
497 : rTime.tm_sec = static_cast < sal_uInt32 > ( 2 * (nDosDate & 0x1F) );
498 : }
499 : */
500 :
501 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|