1 #include <QCoreApplication>
2 #include <QTest>
3 
4 #include "sevenzipfile.h"
5 
SevenZipFile(QString fileName,QObject * parent)6 SevenZipFile::SevenZipFile(QString fileName, QObject *parent) :
7 	QObject(parent),
8 	m_blockIndex(0xFFFFFFFF),
9 	m_bufferSize(0),
10 	m_sizeProcessed(0),
11 	m_buffer(0),
12 	m_fileName(fileName),
13 	m_extractor(0),
14 	m_isOpen(false),
15 	m_firstExtraction(true),
16 	m_fillingDictionary(false)
17 {
18 	m_allocImp.Alloc = SzAlloc;
19 	m_allocImp.Free = SzFree;
20 	m_allocTempImp.Alloc = SzAllocTemp;
21 	m_allocTempImp.Free = SzFreeTemp;
22 }
23 
~SevenZipFile()24 SevenZipFile::~SevenZipFile()
25 {
26 	IAlloc_Free(&m_allocImp, m_buffer);
27 }
28 
read(QString name,QByteArray * buffer)29 quint64 SevenZipFile::read(QString name, QByteArray *buffer)
30 {
31 	m_lastError.clear();
32 	int index = indexOfName(name);
33 	if ( index >= 0 )
34 		return read(index, buffer);
35 	else {
36 		m_lastError = tr("file name '%1' not found").arg(name);
37 		emit error(lastError());
38 		return 0;
39 	}
40 }
41 
read(uint index,QByteArray * buffer,bool * async)42 quint64 SevenZipFile::read(uint index, QByteArray *buffer, bool *async)
43 {
44 	m_lastError.clear();
45 	if ( !isOpen() ) {
46 		m_lastError = tr("archive not open");
47 		emit error(lastError());
48 		return 0;
49 	}
50 	if ( !buffer ) {
51 		m_lastError = tr("null-buffer not allowed");
52 		emit error(lastError());
53 		return 0;
54 	}
55 	if ( m_firstExtraction ) {
56 		// do the first extraction in a separate thread because 7z decompresses the *complete* LZMA stream at once (and returns pointers to already decompressed data on subsequent extractions)
57 		if ( !m_extractor ) {
58 			m_extractor = new SevenZipExtractorThread(this);
59 			connect(m_extractor, SIGNAL(extracted(uint)), this, SLOT(asyncExtractionFinished(uint)));
60 			connect(m_extractor, SIGNAL(failed(uint)), this, SLOT(asyncExtractionFinished(uint)));
61 			m_extractor->setParams(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
62 		}
63 		if ( async && *async ) {
64 			if ( m_extractor->isActive() )
65 				return 0;
66 			if ( m_extractor->fileCount() == 0 ) {
67 				buffer->clear();
68 				m_sizeProcessed = 0;
69 				m_fillingDictionary = true;
70 				m_extractor->waitCondition().wakeAll();
71 			}
72 			if ( isFillingDictionary() )
73 				return 0;
74 			if ( m_extractor->result() == SZ_OK )
75 				buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
76 			else {
77 				m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(m_extractor->result());
78 				emit error(lastError());
79 				m_sizeProcessed = 0;
80 			}
81 			delete m_extractor;
82 			m_extractor = 0;
83 			m_firstExtraction = false;
84 			*async = false;
85 			return m_sizeProcessed;
86 		} else {
87 			m_fillingDictionary = true;
88 			buffer->clear();
89 			m_sizeProcessed = 0;
90 			while ( isFillingDictionary() ) {
91 				m_extractor->waitCondition().wakeAll();
92 				QTest::qWait(25);
93 				qApp->processEvents();
94 			}
95 			if ( m_extractor->result() == SZ_OK )
96 				buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
97 			else {
98 				m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(m_extractor->result());
99 				emit error(lastError());
100 				m_sizeProcessed = 0;
101 			}
102 			delete m_extractor;
103 			m_extractor = 0;
104 			m_firstExtraction = false;
105 			if ( async )
106 				*async = false;
107 			return m_sizeProcessed;
108 		}
109 	} else {
110 		buffer->clear();
111 		m_sizeProcessed = 0;
112 		SRes result = SzArEx_Extract(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
113 		if ( result == SZ_OK )
114 			buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
115 		else {
116 			m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(result);
117 			emit error(lastError());
118 			m_sizeProcessed = 0;
119 		}
120 		if ( async )
121 			*async = false;
122 		return m_sizeProcessed;
123 	}
124 }
125 
readBig(uint index,BigByteArray * buffer,bool * async)126 quint64 SevenZipFile::readBig(uint index, BigByteArray *buffer, bool *async)
127 {
128 	m_lastError.clear();
129 	if ( !isOpen() ) {
130 		m_lastError = tr("archive not open");
131 		emit error(lastError());
132 		return 0;
133 	}
134 	if ( !buffer ) {
135 		m_lastError = tr("null-buffer not allowed");
136 		emit error(lastError());
137 		return 0;
138 	}
139 	if ( m_firstExtraction ) {
140 		// do the first extraction in a separate thread because 7z decompresses the *complete* LZMA stream at once (and returns pointers to already decompressed data on subsequent extractions)
141 		if ( !m_extractor ) {
142 			m_extractor = new SevenZipExtractorThread(this);
143 			connect(m_extractor, SIGNAL(extracted(uint)), this, SLOT(asyncExtractionFinished(uint)));
144 			connect(m_extractor, SIGNAL(failed(uint)), this, SLOT(asyncExtractionFinished(uint)));
145 			m_extractor->setParams(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
146 		}
147 		if ( async && *async ) {
148 			if ( m_extractor->isActive() )
149 				return 0;
150 			if ( m_extractor->fileCount() == 0 ) {
151 				buffer->clear();
152 				m_sizeProcessed = 0;
153 				m_fillingDictionary = true;
154 				m_extractor->waitCondition().wakeAll();
155 			}
156 			if ( isFillingDictionary() )
157 				return 0;
158 			if ( m_extractor->result() == SZ_OK )
159 				buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
160 			else {
161 				m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(m_extractor->result());
162 				emit error(lastError());
163 				m_sizeProcessed = 0;
164 			}
165 			delete m_extractor;
166 			m_extractor = 0;
167 			m_firstExtraction = false;
168 			*async = false;
169 			return m_sizeProcessed;
170 		} else {
171 			m_fillingDictionary = true;
172 			buffer->clear();
173 			m_sizeProcessed = 0;
174 			while ( isFillingDictionary() ) {
175 				m_extractor->waitCondition().wakeAll();
176 				QTest::qWait(25);
177 				qApp->processEvents();
178 			}
179 			if ( m_extractor->result() == SZ_OK )
180 				buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
181 			else {
182 				m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(m_extractor->result());
183 				emit error(lastError());
184 				m_sizeProcessed = 0;
185 			}
186 			delete m_extractor;
187 			m_extractor = 0;
188 			m_firstExtraction = false;
189 			if ( async )
190 				*async = false;
191 			return m_sizeProcessed;
192 		}
193 	} else {
194 		buffer->clear();
195 		m_sizeProcessed = 0;
196 		SRes result = SzArEx_Extract(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
197 		if ( result == SZ_OK )
198 			buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
199 		else {
200 			m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) +  " - " + errorCodeToString(result);
201 			emit error(lastError());
202 			m_sizeProcessed = 0;
203 		}
204 		if ( async )
205 			*async = false;
206 		return m_sizeProcessed;
207 	}
208 }
209 
errorCodeToString(SRes errorCode)210 QString SevenZipFile::errorCodeToString(SRes errorCode)
211 {
212 	switch ( errorCode ) {
213 		case SZ_OK:
214 			return tr("no error");
215 		case SZ_ERROR_DATA:
216 			return tr("incorrect data");
217 		case SZ_ERROR_MEM:
218 			return tr("out of memory");
219 		case SZ_ERROR_CRC:
220 			return tr("incorrect CRC");
221 		case SZ_ERROR_UNSUPPORTED:
222 			return tr("unsupported compression");
223 		case SZ_ERROR_PARAM:
224 			return tr("incorrect properties");
225 		case SZ_ERROR_INPUT_EOF:
226 			return tr("premature end-of-file (input)");
227 		case SZ_ERROR_OUTPUT_EOF:
228 			return tr("premature end-of-file (output)");
229 		case SZ_ERROR_READ:
230 			return tr("failed reading");
231 		case SZ_ERROR_WRITE:
232 			return tr("failed writing");
233 		case SZ_ERROR_PROGRESS:
234 			return tr("failed signalling progress");
235 		case SZ_ERROR_FAIL:
236 			return tr("fatal error");
237 		case SZ_ERROR_THREAD:
238 			return tr("thread error");
239 		case SZ_ERROR_ARCHIVE:
240 			return tr("invalid archive structure");
241 		case SZ_ERROR_NO_ARCHIVE:
242 			return tr("invalid header structure");
243 		default:
244 			return tr("unknown error");
245 	}
246 }
247 
open(QString fileName)248 bool SevenZipFile::open(QString fileName)
249 {
250 	m_lastError.clear();
251 
252 	if ( isOpen() )
253 		close();
254 
255 	if ( !fileName.isEmpty() )
256 		m_fileName = fileName;
257 
258 	if ( m_fileName.isEmpty() ) {
259 		m_lastError = tr("no file name specified");
260 		emit error(lastError());
261 		return false;
262 	}
263 
264 	if ( InFile_Open(&m_archiveStream.file, m_fileName.toUtf8().constData()) ) {
265 		m_lastError = tr("can't open archive '%1'").arg(m_fileName);
266 		emit error(lastError());
267 		return false;
268 	}
269 
270 	FileInStream_CreateVTable(&m_archiveStream);
271 	LookToRead_CreateVTable(&m_lookStream, false);
272 	m_lookStream.realStream = &m_archiveStream.s;
273 	LookToRead_Init(&m_lookStream);
274 	CrcGenerateTable();
275 	SzArEx_Init(db());
276 
277 	SRes result = SzArEx_Open(db(), &m_lookStream.s, &m_allocImp, &m_allocTempImp);
278 
279 	if ( result == SZ_OK ) {
280 		m_isOpen = true;
281 		createEntryList();
282 		emit opened();
283 		return true;
284 	} else {
285 		m_lastError = tr("can't open archive '%1'").arg(m_fileName) + ", " + errorCodeToString(result);
286 		emit error(lastError());
287 		return false;
288 	}
289 }
290 
close()291 void SevenZipFile::close()
292 {
293 	m_lastError.clear();
294 
295 	if ( isOpen() ) {
296 		File_Close(&m_archiveStream.file);
297 		SzArEx_Free(db(), &m_allocImp);
298 		emit closed();
299 	}
300 
301 	entryList().clear();
302 	m_nameToIndexCache.clear();
303 	m_crcToIndexCache.clear();
304 	m_isOpen = false;
305 	m_firstExtraction = true;
306 }
307 
asyncExtractionFinished(uint)308 void SevenZipFile::asyncExtractionFinished(uint /*index*/)
309 {
310 	m_fillingDictionary = false;
311 	emit dataReady();
312 }
313 
314 #define QMC2_SEVENZIP_PERIOD_4       (4 * 365 + 1)
315 #define QMC2_SEVENZIP_PERIOD_100     (QMC2_SEVENZIP_PERIOD_4 * 25 - 1)
316 #define QMC2_SEVENZIP_PERIOD_400     (QMC2_SEVENZIP_PERIOD_100 * 4 + 1)
317 
convertFileTime(const CNtfsFileTime * ft)318 QDateTime SevenZipFile::convertFileTime(const CNtfsFileTime *ft)
319 {
320 	unsigned year, mon, day, hour, min, sec;
321 	UInt64 v64 = (ft->Low | ((UInt64)ft->High << 32)) / 10000000;
322 	Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
323 	unsigned t;
324 	UInt32_7z v;
325 	sec = (unsigned)(v64 % 60);
326 	v64 /= 60;
327 	min = (unsigned)(v64 % 60);
328 	v64 /= 60;
329 	hour = (unsigned)(v64 % 24);
330 	v64 /= 24;
331 	v = (UInt32_7z)v64;
332 	year = (unsigned)(1601 + v / QMC2_SEVENZIP_PERIOD_400 * 400);
333 	v %= QMC2_SEVENZIP_PERIOD_400;
334 	t = v / QMC2_SEVENZIP_PERIOD_100;
335 	if ( t == 4 )
336 		t = 3;
337 	year += t * 100;
338 	v -= t * QMC2_SEVENZIP_PERIOD_100;
339 	t = v / QMC2_SEVENZIP_PERIOD_4;
340 	if ( t == 25 )
341 		t = 24;
342 	year += t * 4;
343 	v -= t * QMC2_SEVENZIP_PERIOD_4;
344 	t = v / 365;
345 	if ( t == 4 )
346 		t =  3;
347 	year += t;
348 	v -= t * 365;
349 	if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
350 		ms[1] = 29;
351 	for (mon = 1; mon <= 12; mon++) {
352 		unsigned s = ms[mon - 1];
353 		if ( v < s )
354 			break;
355 		v -= s;
356 	}
357 	day = (unsigned)v + 1;
358 	QDateTime dateTime;
359 	dateTime.setDate(QDate(year, mon, day));
360 	dateTime.setTime(QTime(hour, min, sec));
361 	return dateTime;
362 }
363 
createEntryList()364 void SevenZipFile::createEntryList()
365 {
366 	entryList().clear();
367 	m_nameToIndexCache.clear();
368 	m_crcToIndexCache.clear();
369 	m_crcDuplicates.clear();
370 	if ( !isOpen() )
371 		return;
372 	for (uint i = 0; i < db()->NumFiles; i++)
373 	{
374 		if ( SzArEx_IsDir(db(), i) )
375 			continue;
376 		int fileItemLength = SzArEx_GetFileNameUtf16(db(), i, 0);
377 		UInt16 *tempFileName = (UInt16 *)SzAlloc(0, fileItemLength * sizeof(UInt16));
378 		SzArEx_GetFileNameUtf16(db(), i, tempFileName);
379 		QString fileItemName(QString::fromUtf16(tempFileName, fileItemLength - 1));
380 		m_nameToIndexCache.insert(fileItemName, i);
381 		SzFree(0, tempFileName);
382 		QDateTime dateTime;
383 		if ( SzBitWithVals_Check(&db()->MTime, i) )
384 			dateTime = convertFileTime(&db()->MTime.Vals[i]);
385 		QString crc("00000000");
386 		if ( SzBitWithVals_Check(&db()->CRCs, i) ) {
387 			crc = QString::number(db()->CRCs.Vals[i], 16).rightJustified(8, '0');
388 			m_crcToIndexCache.insert(crc, i);
389 			m_crcDuplicates.insert(crc, m_crcDuplicates.contains(crc));
390 		}
391 		entryList() << SevenZipMetaData(fileItemName, SzArEx_GetFileSize(db(), i), dateTime, crc);
392 	}
393 }
394 
SevenZipExtractorThread(QObject * parent)395 SevenZipExtractorThread::SevenZipExtractorThread(QObject *parent) :
396 	QThread(parent),
397 	m_quitFlag(false),
398 	m_active(false),
399 	m_fileCount(0),
400 	m_result(SZ_OK)
401 {
402 	start();
403 }
404 
~SevenZipExtractorThread()405 SevenZipExtractorThread::~SevenZipExtractorThread()
406 {
407 	setQuitFlag(true);
408 	waitCondition().wakeAll();
409 	wait();
410 	quit();
411 }
412 
setParams(CSzArEx * db,ILookInStream * lookInStream,uint fileIndex,UInt32_7z * blockIndex,Byte ** buffer,size_t * bufferSize,size_t * offset,size_t * sizeProcessed,ISzAlloc * allocImp,ISzAlloc * allocTempImp)413 void SevenZipExtractorThread::setParams(CSzArEx *db, ILookInStream *lookInStream, uint fileIndex, UInt32_7z *blockIndex, Byte **buffer, size_t *bufferSize, size_t *offset, size_t *sizeProcessed, ISzAlloc *allocImp, ISzAlloc *allocTempImp)
414 {
415 	m_db = db;
416 	m_lookInStream = lookInStream;
417 	m_fileIndex = fileIndex;
418 	m_blockIndex = blockIndex;
419 	m_buffer = buffer;
420 	m_bufferSize = bufferSize;
421 	m_offset = offset;
422 	m_sizeProcessed = sizeProcessed;
423 	m_allocImp = allocImp;
424 	m_allocTempImp = allocTempImp;
425 }
426 
run()427 void SevenZipExtractorThread::run()
428 {
429 	while ( !quitFlag() ) {
430 		m_active = false;
431 		waitMutex().lock();
432 		waitCondition().wait(&m_waitMutex);
433 		waitMutex().unlock();
434 		m_active = true;
435 		m_fileCount++;
436 		if ( !quitFlag() ) {
437 			// we assume that all 7z params are set when we are triggered to go on!
438 			m_result = SzArEx_Extract(m_db, m_lookInStream, m_fileIndex, m_blockIndex, m_buffer, m_bufferSize, m_offset, m_sizeProcessed, m_allocImp, m_allocTempImp);
439 			if ( result() == SZ_OK )
440 				emit extracted(m_fileIndex);
441 			else
442 				emit failed(m_fileIndex);
443 		}
444 	}
445 }
446