/* Copyright (C) 2005-2014 Sergey A. Tachenov This file is part of QuaZIP. QuaZIP is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. QuaZIP 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with QuaZIP. If not, see . See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant and contributors, see quazip/(un)zip.h files for details. Basically it's the zlib license. */ #include "quaziodevice.h" #define QUAZIO_INBUFSIZE 4096 #define QUAZIO_OUTBUFSIZE 4096 /// \cond internal class QuaZIODevicePrivate { friend class QuaZIODevice; QuaZIODevicePrivate(QIODevice *io); ~QuaZIODevicePrivate(); QIODevice *io; z_stream zins; z_stream zouts; char *inBuf; int inBufPos; int inBufSize; char *outBuf; int outBufPos; int outBufSize; bool zBufError; bool atEnd; int doFlush(QString &error); }; QuaZIODevicePrivate::QuaZIODevicePrivate(QIODevice *io): io(io), inBuf(NULL), inBufPos(0), inBufSize(0), outBuf(NULL), outBufPos(0), outBufSize(0), zBufError(false), atEnd(false) { zins.zalloc = (alloc_func) NULL; zins.zfree = (free_func) NULL; zins.opaque = NULL; zouts.zalloc = (alloc_func) NULL; zouts.zfree = (free_func) NULL; zouts.opaque = NULL; inBuf = new char[QUAZIO_INBUFSIZE]; outBuf = new char[QUAZIO_OUTBUFSIZE]; #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.setFileName("debug.out"); debug.open(QIODevice::WriteOnly); #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.setFileName("debug.in"); indebug.open(QIODevice::WriteOnly); #endif } QuaZIODevicePrivate::~QuaZIODevicePrivate() { #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.close(); #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.close(); #endif if (inBuf != NULL) delete[] inBuf; if (outBuf != NULL) delete[] outBuf; } int QuaZIODevicePrivate::doFlush(QString &error) { int flushed = 0; while (outBufPos < outBufSize) { int more = io->write(outBuf + outBufPos, outBufSize - outBufPos); if (more == -1) { error = io->errorString(); return -1; } if (more == 0) break; outBufPos += more; flushed += more; } if (outBufPos == outBufSize) { outBufPos = outBufSize = 0; } return flushed; } /// \endcond // #define QUAZIP_ZIODEVICE_DEBUG_OUTPUT // #define QUAZIP_ZIODEVICE_DEBUG_INPUT #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT #include static QFile debug; #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT #include static QFile indebug; #endif QuaZIODevice::QuaZIODevice(QIODevice *io, QObject *parent): QIODevice(parent), d(new QuaZIODevicePrivate(io)) { connect(io, SIGNAL(readyRead()), SIGNAL(readyRead())); } QuaZIODevice::~QuaZIODevice() { if (isOpen()) close(); delete d; } QIODevice *QuaZIODevice::getIoDevice() const { return d->io; } bool QuaZIODevice::open(QIODevice::OpenMode mode) { if ((mode & QIODevice::Append) != 0) { setErrorString(trUtf8("QIODevice::Append is not supported for" " QuaZIODevice")); return false; } if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite) { setErrorString(trUtf8("QIODevice::ReadWrite is not supported for" " QuaZIODevice")); return false; } if ((mode & QIODevice::ReadOnly) != 0) { if (inflateInit(&d->zins) != Z_OK) { setErrorString(d->zins.msg); return false; } } if ((mode & QIODevice::WriteOnly) != 0) { if (deflateInit(&d->zouts, Z_DEFAULT_COMPRESSION) != Z_OK) { setErrorString(d->zouts.msg); return false; } } return QIODevice::open(mode); } void QuaZIODevice::close() { if ((openMode() & QIODevice::ReadOnly) != 0) { if (inflateEnd(&d->zins) != Z_OK) { setErrorString(d->zins.msg); } } if ((openMode() & QIODevice::WriteOnly) != 0) { flush(); if (deflateEnd(&d->zouts) != Z_OK) { setErrorString(d->zouts.msg); } } QIODevice::close(); } qint64 QuaZIODevice::readData(char *data, qint64 maxSize) { int read = 0; while (read < maxSize) { if (d->inBufPos == d->inBufSize) { d->inBufPos = 0; d->inBufSize = d->io->read(d->inBuf, QUAZIO_INBUFSIZE); if (d->inBufSize == -1) { d->inBufSize = 0; setErrorString(d->io->errorString()); return -1; } if (d->inBufSize == 0) break; } while (read < maxSize && d->inBufPos < d->inBufSize) { d->zins.next_in = (Bytef *) (d->inBuf + d->inBufPos); d->zins.avail_in = d->inBufSize - d->inBufPos; d->zins.next_out = (Bytef *) (data + read); d->zins.avail_out = (uInt) (maxSize - read); // hope it's less than 2GB int more = 0; switch (inflate(&d->zins, Z_SYNC_FLUSH)) { case Z_OK: read = (char *) d->zins.next_out - data; d->inBufPos = (char *) d->zins.next_in - d->inBuf; break; case Z_STREAM_END: read = (char *) d->zins.next_out - data; d->inBufPos = (char *) d->zins.next_in - d->inBuf; d->atEnd = true; return read; case Z_BUF_ERROR: // this should never happen, but just in case if (!d->zBufError) { qWarning("Z_BUF_ERROR detected with %d/%d in/out, weird", d->zins.avail_in, d->zins.avail_out); d->zBufError = true; } memmove(d->inBuf, d->inBuf + d->inBufPos, d->inBufSize - d->inBufPos); d->inBufSize -= d->inBufPos; d->inBufPos = 0; more = d->io->read(d->inBuf + d->inBufSize, QUAZIO_INBUFSIZE - d->inBufSize); if (more == -1) { setErrorString(d->io->errorString()); return -1; } if (more == 0) return read; d->inBufSize += more; break; default: setErrorString(QString::fromLocal8Bit(d->zins.msg)); return -1; } } } #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.write(data, read); #endif return read; } qint64 QuaZIODevice::writeData(const char *data, qint64 maxSize) { int written = 0; QString error; if (d->doFlush(error) == -1) { setErrorString(error); return -1; } while (written < maxSize) { // there is some data waiting in the output buffer if (d->outBufPos < d->outBufSize) return written; d->zouts.next_in = (Bytef *) (data + written); d->zouts.avail_in = (uInt) (maxSize - written); // hope it's less than 2GB d->zouts.next_out = (Bytef *) d->outBuf; d->zouts.avail_out = QUAZIO_OUTBUFSIZE; switch (deflate(&d->zouts, Z_NO_FLUSH)) { case Z_OK: written = (char *) d->zouts.next_in - data; d->outBufSize = (char *) d->zouts.next_out - d->outBuf; break; default: setErrorString(QString::fromLocal8Bit(d->zouts.msg)); return -1; } if (d->doFlush(error) == -1) { setErrorString(error); return -1; } } #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.write(data, written); #endif return written; } bool QuaZIODevice::flush() { QString error; if (d->doFlush(error) < 0) { setErrorString(error); return false; } // can't flush buffer, some data is still waiting if (d->outBufPos < d->outBufSize) return true; Bytef c = 0; d->zouts.next_in = &c; // fake input buffer d->zouts.avail_in = 0; // of zero size do { d->zouts.next_out = (Bytef *) d->outBuf; d->zouts.avail_out = QUAZIO_OUTBUFSIZE; switch (deflate(&d->zouts, Z_SYNC_FLUSH)) { case Z_OK: d->outBufSize = (char *) d->zouts.next_out - d->outBuf; if (d->doFlush(error) < 0) { setErrorString(error); return false; } if (d->outBufPos < d->outBufSize) return true; break; case Z_BUF_ERROR: // nothing to write? return true; default: setErrorString(QString::fromLocal8Bit(d->zouts.msg)); return false; } } while (d->zouts.avail_out == 0); return true; } bool QuaZIODevice::isSequential() const { return true; } bool QuaZIODevice::atEnd() const { // Here we MUST check QIODevice::bytesAvailable() because WE // might have reached the end, but QIODevice didn't-- // it could have simply pre-buffered all remaining data. return (openMode() == NotOpen) || (QIODevice::bytesAvailable() == 0 && d->atEnd); } qint64 QuaZIODevice::bytesAvailable() const { // If we haven't recevied Z_STREAM_END, it means that // we have at least one more input byte available. // Plus whatever QIODevice has buffered. return (d->atEnd ? 0 : 1) + QIODevice::bytesAvailable(); }