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