LibreOffice Module package (master) 1
ZipOutputStream.cxx
Go to the documentation of this file.
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#include <ZipOutputStream.hxx>
21
22#include <com/sun/star/packages/zip/ZipConstants.hpp>
23#include <com/sun/star/io/IOException.hpp>
24#include <com/sun/star/io/XInputStream.hpp>
26
27#include <osl/time.h>
28#include <osl/thread.hxx>
29
30#include <PackageConstants.hxx>
31#include <ZipEntry.hxx>
32#include <ZipOutputEntry.hxx>
33#include <ZipPackageStream.hxx>
34
35#include <thread>
36
37using namespace com::sun::star;
38using namespace com::sun::star::io;
39using namespace com::sun::star::uno;
40using namespace com::sun::star::packages::zip::ZipConstants;
41
44ZipOutputStream::ZipOutputStream( const uno::Reference < io::XOutputStream > &xOStream )
45: m_xStream(xOStream)
46, mpThreadTaskTag( comphelper::ThreadPool::createThreadTaskTag() )
47, m_aChucker(xOStream)
48, m_pCurrentEntry(nullptr)
49{
50}
51
53{
54}
55
57{
58 if (pEntry->nTime == -1)
59 pEntry->nTime = getCurrentDosTime();
60 if (pEntry->nMethod == -1)
61 pEntry->nMethod = DEFLATED;
62 pEntry->nVersion = 20;
63 pEntry->nFlag = 1 << 11;
64 if (pEntry->nSize == -1 || pEntry->nCompressedSize == -1 ||
65 pEntry->nCrc == -1)
66 {
67 pEntry->nSize = pEntry->nCompressedSize = 0;
68 pEntry->nFlag |= 8;
69 }
70}
71
72void ZipOutputStream::addDeflatingThreadTask( ZipOutputEntryInThread *pEntry, std::unique_ptr<comphelper::ThreadTask> pTask )
73{
75 m_aEntries.push_back(pEntry);
76}
77
79{
80 m_aChucker.WriteBytes( rBuffer );
81}
82
83void ZipOutputStream::rawCloseEntry( bool bEncrypt )
84{
85 assert(m_pCurrentEntry && "Forgot to call writeLOC()?");
86 if ( m_pCurrentEntry->nMethod == DEFLATED && ( m_pCurrentEntry->nFlag & 8 ) )
88
89 if (bEncrypt)
90 m_pCurrentEntry->nMethod = STORED;
91
92 m_pCurrentEntry = nullptr;
93}
94
95void ZipOutputStream::consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread> pCandidate)
96{
97 //Any exceptions thrown in the threads were caught and stored for now
98 const std::exception_ptr& rCaughtException(pCandidate->getParallelDeflateException());
99 if (rCaughtException)
100 {
101 m_aDeflateException = rCaughtException; // store it for later throwing
102 // the exception handler in DeflateThreadTask should have cleaned temp file
103 return;
104 }
105
106 writeLOC(pCandidate->getZipEntry(), pCandidate->isEncrypt());
107
108 sal_Int32 nRead;
109 uno::Sequence< sal_Int8 > aSequence(n_ConstBufferSize);
110 uno::Reference< io::XInputStream > xInput = pCandidate->getData();
111 do
112 {
113 nRead = xInput->readBytes(aSequence, n_ConstBufferSize);
114 if (nRead < n_ConstBufferSize)
115 aSequence.realloc(nRead);
116
117 rawWrite(aSequence);
118 }
119 while (nRead == n_ConstBufferSize);
120 xInput.clear();
121
122 rawCloseEntry(pCandidate->isEncrypt());
123
124 pCandidate->getZipPackageStream()->successfullyWritten(pCandidate->getZipEntry());
125 pCandidate->deleteBufferFile();
126}
127
129{
130 std::vector< ZipOutputEntryInThread* > aNonFinishedEntries;
131
133 {
134 if(pEntry->isFinished())
135 {
136 consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pEntry));
137 }
138 else
139 {
140 aNonFinishedEntries.push_back(pEntry);
141 }
142 }
143
144 // always reset to non-consumed entries
145 m_aEntries = aNonFinishedEntries;
146}
147
149{
150 while(m_aEntries.size() > nThreadTasks)
151 {
153
154 if(m_aEntries.size() > nThreadTasks)
155 {
156 std::this_thread::sleep_for(std::chrono::microseconds(100));
157 }
158 }
159}
160
162{
163 assert(!m_aZipList.empty() && "Zip file must have at least one entry!");
164
165 // Wait for all thread tasks to finish & write
167
168 // consume all processed entries
169 while(!m_aEntries.empty())
170 {
171 ZipOutputEntryInThread* pCandidate = m_aEntries.back();
172 m_aEntries.pop_back();
173 consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pCandidate));
174 }
175
176 sal_Int32 nOffset= static_cast < sal_Int32 > (m_aChucker.GetPosition());
177 for (ZipEntry* p : m_aZipList)
178 {
179 writeCEN( *p );
180 delete p;
181 }
182 writeEND( nOffset, static_cast < sal_Int32 > (m_aChucker.GetPosition()) - nOffset);
183 m_aZipList.clear();
184
186 { // throw once all thread tasks are finished and m_aEntries can be released
187 std::rethrow_exception(m_aDeflateException);
188 }
189}
190
191const css::uno::Reference< css::io::XOutputStream >& ZipOutputStream::getStream() const
192{
193 return m_xStream;
194}
195
196void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength)
197{
198 m_aChucker.WriteInt32( ENDSIG );
204 m_aChucker.WriteUInt32( nOffset );
206}
207
208static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated )
209{
210 if( nNum >= 0xffffffff )
211 {
212 *pIsTruncated = true;
213 return 0xffffffff;
214 }
215 else
216 return static_cast< sal_uInt32 >( nNum );
217}
218
220{
222 throw IOException("Unexpected character is used in file name." );
223
224 OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
225 sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
226
227 m_aChucker.WriteInt32( CENSIG );
230 m_aChucker.WriteInt16( rEntry.nFlag );
231 m_aChucker.WriteInt16( rEntry.nMethod );
232 bool bWrite64Header = false;
233
234 m_aChucker.WriteUInt32( rEntry.nTime );
235 m_aChucker.WriteUInt32( rEntry.nCrc );
236 m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) );
237 m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) );
238 sal_uInt32 nOffset32bit = getTruncated( rEntry.nOffset, &bWrite64Header );
239 m_aChucker.WriteInt16(nNameLength);
240 m_aChucker.WriteInt16( bWrite64Header? 32 : 0 ); //in ZIP64 case extra field is 32byte
245 m_aChucker.WriteUInt32( nOffset32bit );
246
247 Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() );
248 m_aChucker.WriteBytes( aSequence );
249
250 if (bWrite64Header)
251 {
252 writeExtraFields( rEntry );
253 }
254}
255
257{
258 bool bWrite64Header = false;
259
260 m_aChucker.WriteInt32( EXTSIG );
261 m_aChucker.WriteUInt32( rEntry.nCrc );
262 // For ZIP64(tm) format archives, the compressed and uncompressed sizes are 8 bytes each.
263 // TODO: Not sure if this is the "when ZIP64(tm) format is used"
264 bWrite64Header = rEntry.nCompressedSize >= 0x100000000 || rEntry.nSize >= 0x100000000;
265 if (!bWrite64Header)
266 {
267 m_aChucker.WriteUInt32( static_cast<sal_uInt32>(rEntry.nCompressedSize) );
268 m_aChucker.WriteUInt32( static_cast<sal_uInt32>(rEntry.nSize) );
269 }
270 else
271 {
273 m_aChucker.WriteUInt64( rEntry.nSize );
274 }
275}
276
278{
279 //Could contain more fields, now we only save Zip64 extended information
280 m_aChucker.WriteInt16( 1 ); //id of Zip64 extended information extra field
281 m_aChucker.WriteInt16( 28 ); //data size of this field = 3*8+4 byte
282 m_aChucker.WriteUInt64( rEntry.nSize );
285 m_aChucker.WriteInt32( 0 ); //Number of the disk on which this file starts
286}
287
288void ZipOutputStream::writeLOC( ZipEntry *pEntry, bool bEncrypt )
289{
290 assert(!m_pCurrentEntry && "Forgot to close an entry with rawCloseEntry()?");
291 m_pCurrentEntry = pEntry;
292 m_aZipList.push_back( m_pCurrentEntry );
293 const ZipEntry &rEntry = *m_pCurrentEntry;
294
296 throw IOException("Unexpected character is used in file name." );
297
298 OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 );
299 sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() );
300
301 m_aChucker.WriteInt32( LOCSIG );
303
304 m_aChucker.WriteInt16( rEntry.nFlag );
305 // If it's an encrypted entry, we pretend its stored plain text
306 if (bEncrypt)
307 m_aChucker.WriteInt16( STORED );
308 else
309 m_aChucker.WriteInt16( rEntry.nMethod );
310
311 bool bWrite64Header = false;
312
313 m_aChucker.WriteUInt32( rEntry.nTime );
314 if ((rEntry.nFlag & 8) == 8 )
315 {
319 }
320 else
321 {
322 m_aChucker.WriteUInt32( rEntry.nCrc );
323 m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) );
324 m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) );
325 }
326 m_aChucker.WriteInt16( nNameLength );
327 m_aChucker.WriteInt16( bWrite64Header ? 32 : 0 );
328
329 Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() );
330 m_aChucker.WriteBytes( aSequence );
331
332 m_pCurrentEntry->nOffset = m_aChucker.GetPosition() - (LOCHDR + nNameLength);
333
334 if (bWrite64Header)
335 {
336 writeExtraFields(rEntry);
337 }
338}
339
341{
342 oslDateTime aDateTime;
343 TimeValue aTimeValue;
344 osl_getSystemTime ( &aTimeValue );
345 osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime);
346
347 // at year 2108, there is an overflow
348 // -> some decision needs to be made
349 // how to handle the ZIP file format (just overflow?)
350
351 // if the current system time is before 1980,
352 // then the time traveller will have to make a decision
353 // how to handle the ZIP file format before it is invented
354 // (just underflow?)
355
356 assert(aDateTime.Year > 1980 && aDateTime.Year < 2108);
357
358 sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year);
359
360 if (nYear>=1980)
361 nYear-=1980;
362 else if (nYear>=80)
363 {
364 nYear-=80;
365 }
366 sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) +
367 ( 32 * (aDateTime.Month)) +
368 ( 512 * nYear ) ) << 16) |
369 ( ( aDateTime.Seconds/2) +
370 ( 32 * aDateTime.Minutes) +
371 ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) );
372 return nResult;
373}
374
375/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
const sal_Int32 n_ConstBufferSize
static sal_uInt32 getTruncated(sal_Int64 nNum, bool *pIsTruncated)
sal_Int64 GetPosition()
Definition: ByteChucker.cxx:48
void WriteUInt64(sal_uInt64 nuInt64)
Definition: ByteChucker.hxx:74
void WriteInt32(sal_Int32 nInt32)
Definition: ByteChucker.hxx:56
void WriteUInt32(sal_uInt32 nuInt32)
Definition: ByteChucker.hxx:65
void WriteInt16(sal_Int16 nInt16)
Definition: ByteChucker.hxx:49
void WriteBytes(const css::uno::Sequence< sal_Int8 > &aData)
Definition: ByteChucker.cxx:43
void writeLOC(ZipEntry *pEntry, bool bEncrypt=false)
void consumeFinishedScheduledThreadTaskEntries()
void rawCloseEntry(bool bEncrypt=false)
const css::uno::Reference< css::io::XOutputStream > & getStream() const
ZipEntry * m_pCurrentEntry
void writeDataDescriptor(const ZipEntry &rEntry)
void writeCEN(const ZipEntry &rEntry)
void writeEND(sal_uInt32 nOffset, sal_uInt32 nLength)
::std::vector< ZipEntry * > m_aZipList
void rawWrite(const css::uno::Sequence< sal_Int8 > &rBuffer)
std::vector< ZipOutputEntryInThread * > m_aEntries
static void setEntry(ZipEntry *pEntry)
std::shared_ptr< comphelper::ThreadTaskTag > mpThreadTaskTag
void writeExtraFields(const ZipEntry &rEntry)
void addDeflatingThreadTask(ZipOutputEntryInThread *pEntry, std::unique_ptr< comphelper::ThreadTask > pThreadTask)
ZipOutputStream(const css::uno::Reference< css::io::XOutputStream > &xOStream)
This class is used to write Zip files.
void consumeScheduledThreadTaskEntry(std::unique_ptr< ZipOutputEntryInThread > pCandidate)
ByteChucker m_aChucker
static sal_uInt32 getCurrentDosTime()
css::uno::Reference< css::io::XOutputStream > m_xStream
void reduceScheduledThreadTasksToGivenNumberOrLess(std::size_t nThreadTasks)
std::exception_ptr m_aDeflateException
static bool IsValidZipEntryFileName(std::u16string_view aName, bool bSlashAllowed)
static ThreadPool & getSharedOptimalPool()
void waitUntilDone(const std::shared_ptr< ThreadTaskTag > &, bool bJoin=true)
void pushTask(std::unique_ptr< ThreadTask > pTask)
void * p
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
sal_Int64 nCompressedSize
Definition: ZipEntry.hxx:31
sal_Int16 nVersion
Definition: ZipEntry.hxx:26
sal_Int64 nOffset
Definition: ZipEntry.hxx:33
sal_Int32 nCrc
Definition: ZipEntry.hxx:30
sal_Int64 nSize
Definition: ZipEntry.hxx:32
sal_Int32 nTime
Definition: ZipEntry.hxx:29
OUString sPath
Definition: ZipEntry.hxx:36
sal_Int16 nFlag
Definition: ZipEntry.hxx:27
sal_Int16 nMethod
Definition: ZipEntry.hxx:28
signed char sal_Int8
Reference< XStream > m_xStream
sal_Int32 nLength