//============================================================================= // // File : KviPackageReader.cpp // Creation date : Tue 26 Dec 2006 05:33:33 by Szymon Stefanek // // This file is part of the KVIrc IRC Client distribution // Copyright (C) 2006-2010 Szymon Stefanek // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "KviPackageReader.h" #include "KviFile.h" #include "KviFileUtils.h" #include "KviLocale.h" #include "kvi_inttypes.h" #include #ifdef COMPILE_ZLIB_SUPPORT #include #endif // // See KviPackageIOEngine.cpp for the description of the KVIrc package file // KviPackageReader::KviPackageReader() : KviPackageIOEngine() { } KviPackageReader::~KviPackageReader() = default; bool KviPackageReader::readHeaderInternal(KviFile * pFile, const QString &) { // read the PackageHeader // Magic char magic[4]; if(pFile->read(magic, 4) != 4) return readError(); if((magic[0] != 'K') || (magic[1] != 'V') || (magic[2] != 'P') || (magic[3] != 'F')) { setLastError(__tr2qs("The file specified is not a valid KVIrc package")); return false; } // Version kvi_u32_t uVersion; if(!pFile->load(uVersion)) return readError(); if(uVersion != 0x1) { setLastError(__tr2qs("The package has an invalid version number, it might have been created by a newer KVIrc")); return false; } // Flags kvi_u32_t uFlags; if(!pFile->load(uFlags)) return readError(); // we ignore them at the moment // read PackageInfo // InfoFieldCount kvi_u32_t uCount; if(!pFile->load(uCount)) return writeError(); stringInfoFields()->clear(); binaryInfoFields()->clear(); kvi_u32_t uIdx = 0; while(uIdx < uCount) { QString szKey; if(!pFile->load(szKey)) return readError(); kvi_u32_t uFieldType; if(!pFile->load(uFieldType)) return readError(); switch(uFieldType) { case KVI_PACKAGE_INFOFIELD_TYPE_STRING: { QString szValue; if(!pFile->load(szValue)) return readError(); stringInfoFields()->replace(szKey, new QString(szValue)); } break; case KVI_PACKAGE_INFOFIELD_TYPE_BINARYBUFFER: { QByteArray * pbValue = new QByteArray(); if(!pFile->load(*pbValue)) { delete pbValue; return readError(); } binaryInfoFields()->replace(szKey, pbValue); } break; default: setLastError(__tr2qs("Invalid info field: the package is probably corrupt")); break; } uIdx++; } return true; } bool KviPackageReader::readHeader(const QString & szLocalFileName) { KviFile f(szLocalFileName); if(!f.open(QFile::ReadOnly)) { setLastError(__tr2qs("Can't open file for reading")); return false; } return readHeaderInternal(&f, szLocalFileName); } #define BUFFER_SIZE 32768 bool KviPackageReader::unpackFile(KviFile * pFile, const QString & szUnpackPath) { // Flags kvi_u32_t uFlags; if(!pFile->load(uFlags)) return readError(); #ifndef COMPILE_ZLIB_SUPPORT if(uFlags & KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE) { setLastError(__tr2qs("The package contains compressed data but this executable does not support compression")); return false; } #endif // Path QString szPath; if(!pFile->load(szPath)) return readError(); QString szFileName = szUnpackPath; KviQString::ensureLastCharIs(szFileName, QChar(KVI_PATH_SEPARATOR_CHAR)); szFileName += szPath; // no attacks please :) szFileName.replace(QString("..\\"), QString("")); szFileName.replace(QString("../"), QString("")); KviFileUtils::adjustFilePath(szFileName); int idx = szFileName.lastIndexOf(QChar(KVI_PATH_SEPARATOR_CHAR)); if(idx != -1) { QString szPrefixPath = szFileName.left(idx); if(!KviFileUtils::makeDir(szPrefixPath)) { setLastError(__tr2qs("Failed to create the target directory")); return false; } } KviFile dest(szFileName); if(!dest.open(QFile::WriteOnly | QFile::Truncate)) { setLastError(__tr2qs("Failed to open a source file for reading")); return false; } QString szProgressText = QString(__tr2qs("Unpacking file %1")).arg(szFileName); if(!updateProgress(pFile->pos(), szProgressText)) return false; // aborted // Size kvi_u32_t uSize; if(!pFile->load(uSize)) return readError(); // FilePayload #ifdef COMPILE_ZLIB_SUPPORT if(uFlags & KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE) { //qDebug ("loading compressed data"); int iRemainingSize = uSize; unsigned char ibuffer[BUFFER_SIZE]; unsigned char obuffer[BUFFER_SIZE]; int iToRead = iRemainingSize; if(iToRead > BUFFER_SIZE) iToRead = BUFFER_SIZE; int iReaded = pFile->read((char *)ibuffer, iToRead); iRemainingSize -= iReaded; z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.next_in = ibuffer; zstr.avail_in = iReaded; zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; if(inflateInit(&zstr) != Z_OK) { setLastError(__tr2qs("Compression library initialization error")); return false; } while((iReaded > 0) && (iRemainingSize > 0)) { zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; if(inflate(&zstr, Z_NO_FLUSH) != Z_OK) { setLastError(__tr2qs("Compression library error")); return false; } if(zstr.avail_out < BUFFER_SIZE) { int iDecompressed = zstr.next_out - obuffer; if(dest.write((char *)obuffer, iDecompressed) != iDecompressed) { inflateEnd(&zstr); return writeError(); } } if(zstr.avail_in < BUFFER_SIZE) { int iDataToRead = BUFFER_SIZE - zstr.avail_in; if(iDataToRead < BUFFER_SIZE) { if(ibuffer != zstr.next_in) { // hum, there is still some data in the buffer to be readed // and it is not at the beginning...move it to the beginning of ibuffer memmove(ibuffer, zstr.next_in, zstr.avail_in); } } if(iDataToRead > iRemainingSize) iDataToRead = iRemainingSize; iReaded = pFile->read((char *)(ibuffer + zstr.avail_in), iDataToRead); if(iReaded < 0) { inflateEnd(&zstr); return readError(); } iRemainingSize -= iReaded; zstr.avail_in += iReaded; zstr.next_in = ibuffer; if((zstr.total_in % 2000000) == 0) { QString szTmp = QString(" (%1 of %2 bytes)").arg(zstr.total_in, uSize); QString szPrg = szProgressText + szTmp; if(!updateProgress(pFile->pos(), szPrg)) return false; // aborted } } } // flush pending output zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; int ret; do { ret = inflate(&zstr, Z_FINISH); if((ret == Z_OK) || (ret == Z_STREAM_END) || (ret == Z_BUF_ERROR)) { if(zstr.avail_out < BUFFER_SIZE) { int iDecompressed = zstr.next_out - obuffer; if(dest.write((char *)obuffer, iDecompressed) != iDecompressed) { inflateEnd(&zstr); return writeError(); } } else { //THIS HAPPENS FOR ZERO SIZE FILES qDebug("Hum... internal, rEWq (ret = %d) (avail_out = %d)", ret, zstr.avail_out); inflateEnd(&zstr); setLastError(__tr2qs("Compression library internal error")); return false; } zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; } } while((ret == Z_OK) || (ret == Z_BUF_ERROR)); inflateEnd(&zstr); if(ret != Z_STREAM_END) { setLastError(__tr2qs("Error in compressed file stream")); return false; } } else { #endif //qDebug("Load uncompressed data"); unsigned char buffer[BUFFER_SIZE]; int iTotalFileSize = 0; int iRemainingData = uSize; int iToRead = iRemainingData; if(iToRead > BUFFER_SIZE) iToRead = BUFFER_SIZE; int iReaded = 1; //qDebug("iReaded %i and iToRead %i and index %i",iReaded,iToRead,pFile->pos()); while((iReaded > 0) && (iToRead > 0)) { iReaded = pFile->read((char *)buffer, iToRead); if(iReaded > 0) { iTotalFileSize += iReaded; iRemainingData -= iReaded; if((iTotalFileSize % 3000000) == 0) { QString szTmp = QString(" (%1 of %2 bytes)").arg(iTotalFileSize).arg(uSize); QString szPrg = szProgressText + szTmp; if(!updateProgress(pFile->pos(), szPrg)) return false; // aborted } //qDebug("write file with size %i and name %s",iReaded,dest.fileName().toUtf8().data()); if(dest.write((char *)buffer, iReaded) != iReaded) return writeError(); } //qDebug("Remains %i", iRemainingData); iToRead = iRemainingData; if(iToRead > BUFFER_SIZE) iToRead = BUFFER_SIZE; } //qDebug("finish to read %i and index %i",iReaded,pFile->pos()); #ifdef COMPILE_ZLIB_SUPPORT } #endif dest.close(); return true; } bool KviPackageReader::getStringInfoField(const QString & szName, QString & szBuffer) { QString * pVal = stringInfoFields()->find(szName); if(!pVal) return false; szBuffer = *pVal; return true; } bool KviPackageReader::unpack(const QString & szLocalFileName, const QString & szUnpackPath, kvi_u32_t uUnpackFlags) { bool bRet = unpackInternal(szLocalFileName, szUnpackPath, uUnpackFlags); hideProgressDialog(); return bRet; } bool KviPackageReader::unpackInternal(const QString & szLocalFileName, const QString & szUnpackPath, kvi_u32_t uUnpackFlags) { KviFile f(szLocalFileName); if(!f.open(QFile::ReadOnly)) { setLastError(__tr2qs("Can't open file for reading")); return false; } kvi_file_offset_t size = f.size(); if(!(uUnpackFlags & NoProgressDialog)) { showProgressDialog(__tr2qs("Reading package..."), size); updateProgress(0, __tr2qs("Reading package header")); } if(!readHeaderInternal(&f, szLocalFileName)) return false; if(!updateProgress(f.pos(), __tr2qs("Reading package data"))) return false; // aborted while(!f.atEnd()) { // DataFieldType kvi_u32_t uDataFieldType; if(!f.load(uDataFieldType)) return readError(); // DataFieldLen kvi_u32_t uDataFieldLen; if(!f.load(uDataFieldLen)) return readError(); switch(uDataFieldType) { case KVI_PACKAGE_DATAFIELD_TYPE_FILE: if(!unpackFile(&f, szUnpackPath)) return false; break; default: setLastError(__tr2qs("Invalid data field: the package is probably corrupt")); return false; break; } } return true; }