zlibbytearray.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 **
00010 **                     *       *       *
00011 ** 
00012 **  Zlib support in this class is derived from Tor's torgzip.[ch].
00013 **  Tor is distributed under this license:
00014 ** 
00015 **    Copyright (c) 2001-2004, Roger Dingledine
00016 **    Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
00017 **
00018 **   Redistribution and use in source and binary forms, with or without
00019 **   modification, are permitted provided that the following conditions are
00020 **   met:
00021 **
00022 **     * Redistributions of source code must retain the above copyright
00023 **       notice, this list of conditions and the following disclaimer.
00024 **
00025 **     * Redistributions in binary form must reproduce the above
00026 **       copyright notice, this list of conditions and the following disclaimer
00027 **       in the documentation and/or other materials provided with the
00028 **       distribution.
00029 ** 
00030 **     * Neither the names of the copyright owners nor the names of its
00031 **       contributors may be used to endorse or promote products derived from
00032 **       this software without specific prior written permission.
00033 **
00034 **    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00035 **    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00036 **    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00037 **    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
00038 **    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00039 **    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00040 **    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00041 **    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00042 **    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00043 **    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00044 **    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00045 */
00046 
00047 /*
00048 ** \file zlibbytearray.cpp
00049 ** \version $Id: zlibbytearray.cpp 2444 2008-03-23 04:23:08Z edmanm $
00050 ** \brief Wrapper around QByteArray that adds compression capabilities
00051 */
00052 
00053 #include <QString>
00054 
00055 #include "config.h"
00056 #ifdef HAVE_LIMITS_H
00057 #include <limits.h>
00058 #elif HAVE_SYS_LIMITS_H
00059 #include <sys/limits.h>
00060 #endif
00061 
00062 /* The following check for UINT_MAX is derived from Tor's torint.h. See
00063  * the top of this file for details on Tor's license. */
00064 #ifndef UINT_MAX
00065 #if (SIZEOF_INT == 2)
00066 #define UINT_MAX 0xffffu
00067 #elif (SIZEOF_INT == 4)
00068 #define UINT_MAX 0xffffffffu
00069 #elif (SIZEOF_INT == 8)
00070 #define UINT_MAX 0xffffffffffffffffu
00071 #else
00072 #error "Your platform uses a sizeof(int) that we don't understand."
00073 #endif
00074 #endif
00075 
00076 #include "zlib.h"
00077 #include "zlibbytearray.h"
00078 
00079 
00080 /** Constructor */
00081 ZlibByteArray::ZlibByteArray(QByteArray data)
00082 : QByteArray(data)
00083 {
00084 }
00085 
00086 /** Return the 'bits' value to tell zlib to use <b>method</b>.*/
00087 int
00088 ZlibByteArray::methodBits(CompressionMethod method)
00089 {
00090   /* Bits+16 means "use gzip" in zlib >= 1.2 */
00091   return (method == Gzip ? 15+16 : 15);
00092 }
00093 
00094 /** Returns a string description of <b>method</b>. */
00095 QString
00096 ZlibByteArray::methodString(CompressionMethod method)
00097 {
00098   switch (method) {
00099     case None:  return "None";
00100     case Zlib:  return "Zlib";
00101     case Gzip:  return "Gzip";
00102     default:    return "Unknown";
00103   }
00104 }
00105 
00106 /** Returns true if the Zlib compression library is available and usable. */
00107 bool
00108 ZlibByteArray::isZlibAvailable()
00109 {
00110   static int isZlibAvailable = -1;
00111   if (isZlibAvailable >= 0)
00112     return isZlibAvailable;
00113 
00114   /* From zlib.h:
00115    * "The application can compare zlibVersion and ZLIB_VERSION for consistency.
00116    * If the first character differs, the library code actually used is
00117    * not compatible with the zlib.h header file used by the application." */
00118   QString libVersion(zlibVersion());
00119   QString headerVersion(ZLIB_VERSION);
00120   if (libVersion.isEmpty() || headerVersion.isEmpty() ||
00121       libVersion.at(0) != headerVersion.at(0))
00122     isZlibAvailable = 0;
00123   else
00124     isZlibAvailable = 1;
00125 
00126   return isZlibAvailable;
00127 }
00128 
00129 /** Returns true iff we support gzip-based compression. Otherwise, we need to
00130  * use zlib. */
00131 bool
00132 ZlibByteArray::isGzipSupported()
00133 {
00134   static int isGzipSupported = -1;
00135   if (isGzipSupported >= 0)
00136     return isGzipSupported;
00137 
00138   QString version(zlibVersion());
00139   if (version.startsWith("0.") ||
00140       version.startsWith("1.0") ||
00141       version.startsWith("1.1"))
00142     isGzipSupported = 0;
00143   else
00144     isGzipSupported = 1;
00145 
00146   return isGzipSupported;
00147 }
00148 
00149 /** Compresses the current contents of this object using <b>method</b>. 
00150  * Returns the  compressed data if successful. If an error occurs, this will 
00151  * return an empty QByteArray and set the optional <b>errmsg</b> to a string 
00152  * describing the failure. */
00153 QByteArray
00154 ZlibByteArray::compress(const CompressionMethod method,
00155                         QString *errmsg) const
00156 {
00157   return compress(QByteArray(data()), method, errmsg);
00158 }
00159 
00160 /** Compresses <b>in</b> using <b>method</b>. Returns the compressed data
00161  * if successful. If an error occurs, this will return an empty QByteArray and
00162  * set the optional <b>errmsg</b> to a string describing the failure. */
00163 QByteArray
00164 ZlibByteArray::compress(const QByteArray in,
00165                         const CompressionMethod method,
00166                         QString *errmsg)
00167 {
00168   QByteArray out;
00169   QString errorstr;
00170   struct z_stream_s *stream = NULL;
00171   size_t out_size;
00172   size_t out_len;
00173   size_t in_len = in.length();
00174   off_t offset;
00175   
00176   if (method == None)
00177     return in;
00178   if (method == Gzip && !isGzipSupported()) {
00179     /* Old zlib versions don't support gzip in deflateInit2 */
00180     if (errmsg)
00181       *errmsg = QString("Gzip not supported with zlib %1")
00182                                         .arg(ZLIB_VERSION);
00183     return QByteArray();
00184   }
00185   
00186   stream = new struct z_stream_s;
00187   stream->zalloc = Z_NULL;
00188   stream->zfree = Z_NULL;
00189   stream->opaque = NULL;
00190   stream->next_in = (unsigned char*)in.data();
00191   stream->avail_in = in_len;
00192   
00193   if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED,
00194                    methodBits(method),
00195                    8, Z_DEFAULT_STRATEGY) != Z_OK) {
00196     errorstr = QString("Error from deflateInit2: %1")
00197                  .arg(stream->msg ? stream->msg : "<no message>");
00198     goto err;
00199   }
00200   
00201   /* Guess 50% compression. */
00202   out_size = in_len / 2;
00203   if (out_size < 1024) out_size = 1024;
00204   
00205   out.resize(out_size);
00206   stream->next_out = (unsigned char*)out.data();
00207   stream->avail_out = out_size;
00208   
00209   while (1) {
00210     switch (deflate(stream, Z_FINISH))
00211     {
00212       case Z_STREAM_END:
00213         goto done;
00214       case Z_OK:
00215         /* In case zlib doesn't work as I think .... */
00216         if (stream->avail_out >= stream->avail_in+16)
00217           break;
00218       case Z_BUF_ERROR:
00219         offset = stream->next_out - ((unsigned char*)out.data());
00220         out_size *= 2;
00221         out.resize(out_size);
00222         stream->next_out = (unsigned char*)(out.data() + offset);
00223         if (out_size - offset > UINT_MAX) {
00224           errorstr = 
00225             "Ran over unsigned int limit of zlib while uncompressing";
00226           goto err;
00227         }
00228           stream->avail_out = (unsigned int)(out_size - offset);
00229         break;
00230       default:
00231         errorstr = QString("%1 compression didn't finish: %2")
00232                      .arg(methodString(method))
00233                      .arg(stream->msg ? stream->msg : "<no message>");
00234         goto err;
00235     }
00236   }
00237 done:
00238   out_len = stream->total_out;
00239   if (deflateEnd(stream)!=Z_OK) {
00240     errorstr = "Error freeing zlib structures";
00241     goto err;
00242   }
00243   out.resize(out_len);
00244   delete stream;
00245   return out;
00246 err:
00247   if (stream) {
00248     deflateEnd(stream);
00249     delete stream;
00250   }
00251   if (errmsg)
00252     *errmsg = errorstr;
00253   return QByteArray();
00254 }
00255 
00256 /** Uncompresses the current contents of this object using <b>method</b>. 
00257  * Returns the uncompressed data if successful. If an error occurs, this will 
00258  * return an empty QByteArray and set the optional <b>errmsg</b> to a string 
00259  * describing the failure. */
00260 QByteArray
00261 ZlibByteArray::uncompress(const CompressionMethod method,
00262                           QString *errmsg) const
00263 {
00264   return uncompress(QByteArray(data()), method, errmsg);
00265 }
00266 
00267 /** Uncompresses <b>in</b> using <b>method</b>. Returns the uncompressed data
00268  * if successful. If an error occurs, this will return an empty QByteArray and
00269  * set the optional <b>errmsg</b> to a string describing the failure. */
00270 QByteArray
00271 ZlibByteArray::uncompress(const QByteArray in,
00272                           const CompressionMethod method,
00273                           QString *errmsg)
00274 {
00275   QByteArray out;
00276   QString errorstr;
00277   struct z_stream_s *stream = NULL;
00278   size_t out_size;
00279   size_t out_len;
00280   size_t in_len = in.length();
00281   off_t offset;
00282   int r;
00283   
00284   if (method == None)
00285     return in;
00286   if (method == Gzip && !isGzipSupported()) {
00287     /* Old zlib versions don't support gzip in inflateInit2 */
00288     if (errmsg)
00289       *errmsg = QString("Gzip not supported with zlib %1")
00290                                         .arg(ZLIB_VERSION);
00291     return QByteArray();
00292   }
00293   
00294   stream = new struct z_stream_s;
00295   stream->zalloc = Z_NULL;
00296   stream->zfree = Z_NULL;
00297   stream->opaque = NULL;
00298   stream->msg = NULL;
00299   stream->next_in = (unsigned char*) in.data();
00300   stream->avail_in = in_len;
00301   
00302   if (inflateInit2(stream,
00303                    methodBits(method)) != Z_OK) {
00304     errorstr = QString("Error from inflateInit2: %1")
00305                  .arg(stream->msg ? stream->msg : "<no message>");
00306     goto err;
00307   }
00308   
00309   out_size = in_len * 2;  /* guess 50% compression. */
00310   if (out_size < 1024) out_size = 1024;
00311   
00312   out.resize(out_size);
00313   stream->next_out = (unsigned char*)out.data();
00314   stream->avail_out = out_size;
00315   
00316   while (1) {
00317     switch (inflate(stream, Z_FINISH))
00318     {
00319       case Z_STREAM_END:
00320         if (stream->avail_in == 0)
00321           goto done;
00322         /* There may be more compressed data here. */
00323         if ((r = inflateEnd(stream)) != Z_OK) {
00324           errorstr = "Error freeing zlib structures";
00325           goto err;
00326         }
00327         if (inflateInit2(stream, methodBits(method)) != Z_OK) {
00328           errorstr = QString("Error from second inflateInit2: %1")
00329                        .arg(stream->msg ? stream->msg : "<no message>");
00330           goto err;
00331         }
00332         break;
00333       case Z_OK:
00334         if (stream->avail_in == 0)
00335           goto done;
00336         /* In case zlib doesn't work as I think.... */
00337         if (stream->avail_out >= stream->avail_in+16)
00338           break;
00339         case Z_BUF_ERROR:
00340           if (stream->avail_out > 0) {
00341             errorstr = QString("Possible truncated or corrupt %1 data")
00342                          .arg(methodString(method));
00343             goto err;
00344           }
00345           offset = stream->next_out - (unsigned char*)out.data();
00346           out_size *= 2;
00347           out.resize(out_size);
00348           stream->next_out = (unsigned char*)(out.data() + offset);
00349           if (out_size - offset > UINT_MAX) {
00350             errorstr = 
00351               "Ran over unsigned int limit of zlib while uncompressing";
00352             goto err;
00353           }
00354             stream->avail_out = (unsigned int)(out_size - offset);
00355           break;
00356         default:
00357           errorstr = QString("%1 decompression returned an error: %2")
00358                        .arg(methodString(method))
00359                        .arg(stream->msg ? stream->msg : "<no message>");
00360           goto err;
00361     }
00362   }
00363 done:
00364   out_len = stream->next_out - (unsigned char*)out.data();
00365   r = inflateEnd(stream);
00366   delete stream;
00367   if (r != Z_OK) {
00368     errorstr = "Error freeing zlib structure";
00369     goto err;
00370   }
00371   out.resize(out_len);
00372   return out;
00373 err:
00374   if (stream) {
00375     inflateEnd(stream);
00376     delete stream;
00377   }
00378   if (errmsg)
00379     *errmsg = errorstr;
00380   return QByteArray();
00381 }
00382 

Generated on Wed Nov 26 21:02:39 2008 for Vidalia by  doxygen 1.5.6