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 4203 2010-01-22 18:49:00Z edmanm $
00050 ** \brief Wrapper around QByteArray that adds compression capabilities
00051 */
00052 
00053 #include "config.h"
00054 
00055 #include <QString>
00056 
00057 #ifdef HAVE_LIMITS_H
00058 #include <limits.h>
00059 #elif defined(HAVE_SYS_LIMITS_H)
00060 #include <sys/limits.h>
00061 #endif
00062 
00063 /* The following check for UINT_MAX is derived from Tor's torint.h. See
00064  * the top of this file for details on Tor's license. */
00065 #ifndef UINT_MAX
00066 #if (SIZEOF_INT == 2)
00067 #define UINT_MAX 0xffffu
00068 #elif (SIZEOF_INT == 4)
00069 #define UINT_MAX 0xffffffffu
00070 #elif (SIZEOF_INT == 8)
00071 #define UINT_MAX 0xffffffffffffffffu
00072 #else
00073 #error "Your platform uses a sizeof(int) that we don't understand."
00074 #endif
00075 #endif
00076 
00077 #include "zlib.h"
00078 #include <ZlibByteArray.h>
00079 
00080 
00081 /** Constructor */
00082 ZlibByteArray::ZlibByteArray(QByteArray data)
00083 : QByteArray(data)
00084 {
00085 }
00086 
00087 /** Return the 'bits' value to tell zlib to use <b>method</b>.*/
00088 int
00089 ZlibByteArray::methodBits(CompressionMethod method)
00090 {
00091   /* Bits+16 means "use gzip" in zlib >= 1.2 */
00092   return (method == Gzip ? 15+16 : 15);
00093 }
00094 
00095 /** Returns a string description of <b>method</b>. */
00096 QString
00097 ZlibByteArray::methodString(CompressionMethod method)
00098 {
00099   switch (method) {
00100     case None:  return "None";
00101     case Zlib:  return "Zlib";
00102     case Gzip:  return "Gzip";
00103     default:    return "Unknown";
00104   }
00105 }
00106 
00107 /** Returns true if the Zlib compression library is available and usable. */
00108 bool
00109 ZlibByteArray::isZlibAvailable()
00110 {
00111   static int isZlibAvailable = -1;
00112   if (isZlibAvailable >= 0)
00113     return isZlibAvailable;
00114 
00115   /* From zlib.h:
00116    * "The application can compare zlibVersion and ZLIB_VERSION for consistency.
00117    * If the first character differs, the library code actually used is
00118    * not compatible with the zlib.h header file used by the application." */
00119   QString libVersion(zlibVersion());
00120   QString headerVersion(ZLIB_VERSION);
00121   if (libVersion.isEmpty() || headerVersion.isEmpty() ||
00122       libVersion.at(0) != headerVersion.at(0))
00123     isZlibAvailable = 0;
00124   else
00125     isZlibAvailable = 1;
00126 
00127   return isZlibAvailable;
00128 }
00129 
00130 /** Returns true iff we support gzip-based compression. Otherwise, we need to
00131  * use zlib. */
00132 bool
00133 ZlibByteArray::isGzipSupported()
00134 {
00135   static int isGzipSupported = -1;
00136   if (isGzipSupported >= 0)
00137     return isGzipSupported;
00138 
00139   QString version(zlibVersion());
00140   if (version.startsWith("0.") ||
00141       version.startsWith("1.0") ||
00142       version.startsWith("1.1"))
00143     isGzipSupported = 0;
00144   else
00145     isGzipSupported = 1;
00146 
00147   return isGzipSupported;
00148 }
00149 
00150 /** Compresses the current contents of this object using <b>method</b>. 
00151  * Returns the  compressed data if successful. If an error occurs, this will 
00152  * return an empty QByteArray and set the optional <b>errmsg</b> to a string 
00153  * describing the failure. */
00154 QByteArray
00155 ZlibByteArray::compress(const CompressionMethod method,
00156                         QString *errmsg) const
00157 {
00158   return compress(QByteArray(data()), method, errmsg);
00159 }
00160 
00161 /** Compresses <b>in</b> using <b>method</b>. Returns the compressed data
00162  * if successful. If an error occurs, this will return an empty QByteArray and
00163  * set the optional <b>errmsg</b> to a string describing the failure. */
00164 QByteArray
00165 ZlibByteArray::compress(const QByteArray in,
00166                         const CompressionMethod method,
00167                         QString *errmsg)
00168 {
00169   QByteArray out;
00170   QString errorstr;
00171   struct z_stream_s *stream = NULL;
00172   size_t out_size;
00173   size_t out_len;
00174   size_t in_len = in.length();
00175   off_t offset;
00176   
00177   if (method == None)
00178     return in;
00179   if (method == Gzip && !isGzipSupported()) {
00180     /* Old zlib versions don't support gzip in deflateInit2 */
00181     if (errmsg)
00182       *errmsg = QString("Gzip not supported with zlib %1")
00183                                         .arg(ZLIB_VERSION);
00184     return QByteArray();
00185   }
00186   
00187   stream = new struct z_stream_s;
00188   stream->zalloc = Z_NULL;
00189   stream->zfree = Z_NULL;
00190   stream->opaque = NULL;
00191   stream->next_in = (unsigned char*)in.data();
00192   stream->avail_in = in_len;
00193   
00194   if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED,
00195                    methodBits(method),
00196                    8, Z_DEFAULT_STRATEGY) != Z_OK) {
00197     errorstr = QString("Error from deflateInit2: %1")
00198                  .arg(stream->msg ? stream->msg : "<no message>");
00199     goto err;
00200   }
00201   
00202   /* Guess 50% compression. */
00203   out_size = in_len / 2;
00204   if (out_size < 1024) out_size = 1024;
00205   
00206   out.resize(out_size);
00207   stream->next_out = (unsigned char*)out.data();
00208   stream->avail_out = out_size;
00209   
00210   while (1) {
00211     switch (deflate(stream, Z_FINISH))
00212     {
00213       case Z_STREAM_END:
00214         goto done;
00215       case Z_OK:
00216         /* In case zlib doesn't work as I think .... */
00217         if (stream->avail_out >= stream->avail_in+16)
00218           break;
00219       case Z_BUF_ERROR:
00220         offset = stream->next_out - ((unsigned char*)out.data());
00221         out_size *= 2;
00222         out.resize(out_size);
00223         stream->next_out = (unsigned char*)(out.data() + offset);
00224         if (out_size - offset > UINT_MAX) {
00225           errorstr = 
00226             "Ran over unsigned int limit of zlib while uncompressing";
00227           goto err;
00228         }
00229           stream->avail_out = (unsigned int)(out_size - offset);
00230         break;
00231       default:
00232         errorstr = QString("%1 compression didn't finish: %2")
00233                      .arg(methodString(method))
00234                      .arg(stream->msg ? stream->msg : "<no message>");
00235         goto err;
00236     }
00237   }
00238 done:
00239   out_len = stream->total_out;
00240   if (deflateEnd(stream)!=Z_OK) {
00241     errorstr = "Error freeing zlib structures";
00242     goto err;
00243   }
00244   out.resize(out_len);
00245   delete stream;
00246   return out;
00247 err:
00248   if (stream) {
00249     deflateEnd(stream);
00250     delete stream;
00251   }
00252   if (errmsg)
00253     *errmsg = errorstr;
00254   return QByteArray();
00255 }
00256 
00257 /** Uncompresses the current contents of this object using <b>method</b>. 
00258  * Returns the uncompressed data if successful. If an error occurs, this will 
00259  * return an empty QByteArray and set the optional <b>errmsg</b> to a string 
00260  * describing the failure. */
00261 QByteArray
00262 ZlibByteArray::uncompress(const CompressionMethod method,
00263                           QString *errmsg) const
00264 {
00265   return uncompress(QByteArray(data()), method, errmsg);
00266 }
00267 
00268 /** Uncompresses <b>in</b> using <b>method</b>. Returns the uncompressed data
00269  * if successful. If an error occurs, this will return an empty QByteArray and
00270  * set the optional <b>errmsg</b> to a string describing the failure. */
00271 QByteArray
00272 ZlibByteArray::uncompress(const QByteArray in,
00273                           const CompressionMethod method,
00274                           QString *errmsg)
00275 {
00276   QByteArray out;
00277   QString errorstr;
00278   struct z_stream_s *stream = NULL;
00279   size_t out_size;
00280   size_t out_len;
00281   size_t in_len = in.length();
00282   off_t offset;
00283   int r;
00284   
00285   if (method == None)
00286     return in;
00287   if (method == Gzip && !isGzipSupported()) {
00288     /* Old zlib versions don't support gzip in inflateInit2 */
00289     if (errmsg)
00290       *errmsg = QString("Gzip not supported with zlib %1")
00291                                         .arg(ZLIB_VERSION);
00292     return QByteArray();
00293   }
00294   
00295   stream = new struct z_stream_s;
00296   stream->zalloc = Z_NULL;
00297   stream->zfree = Z_NULL;
00298   stream->opaque = NULL;
00299   stream->msg = NULL;
00300   stream->next_in = (unsigned char*) in.data();
00301   stream->avail_in = in_len;
00302   
00303   if (inflateInit2(stream,
00304                    methodBits(method)) != Z_OK) {
00305     errorstr = QString("Error from inflateInit2: %1")
00306                  .arg(stream->msg ? stream->msg : "<no message>");
00307     goto err;
00308   }
00309   
00310   out_size = in_len * 2;  /* guess 50% compression. */
00311   if (out_size < 1024) out_size = 1024;
00312   
00313   out.resize(out_size);
00314   stream->next_out = (unsigned char*)out.data();
00315   stream->avail_out = out_size;
00316   
00317   while (1) {
00318     switch (inflate(stream, Z_FINISH))
00319     {
00320       case Z_STREAM_END:
00321         if (stream->avail_in == 0)
00322           goto done;
00323         /* There may be more compressed data here. */
00324         if ((r = inflateEnd(stream)) != Z_OK) {
00325           errorstr = "Error freeing zlib structures";
00326           goto err;
00327         }
00328         if (inflateInit2(stream, methodBits(method)) != Z_OK) {
00329           errorstr = QString("Error from second inflateInit2: %1")
00330                        .arg(stream->msg ? stream->msg : "<no message>");
00331           goto err;
00332         }
00333         break;
00334       case Z_OK:
00335         if (stream->avail_in == 0)
00336           goto done;
00337         /* In case zlib doesn't work as I think.... */
00338         if (stream->avail_out >= stream->avail_in+16)
00339           break;
00340         case Z_BUF_ERROR:
00341           if (stream->avail_out > 0) {
00342             errorstr = QString("Possible truncated or corrupt %1 data")
00343                          .arg(methodString(method));
00344             goto err;
00345           }
00346           offset = stream->next_out - (unsigned char*)out.data();
00347           out_size *= 2;
00348           out.resize(out_size);
00349           stream->next_out = (unsigned char*)(out.data() + offset);
00350           if (out_size - offset > UINT_MAX) {
00351             errorstr = 
00352               "Ran over unsigned int limit of zlib while uncompressing";
00353             goto err;
00354           }
00355             stream->avail_out = (unsigned int)(out_size - offset);
00356           break;
00357         default:
00358           errorstr = QString("%1 decompression returned an error: %2")
00359                        .arg(methodString(method))
00360                        .arg(stream->msg ? stream->msg : "<no message>");
00361           goto err;
00362     }
00363   }
00364 done:
00365   out_len = stream->next_out - (unsigned char*)out.data();
00366   r = inflateEnd(stream);
00367   delete stream;
00368   if (r != Z_OK) {
00369     errorstr = "Error freeing zlib structure";
00370     goto err;
00371   }
00372   out.resize(out_len);
00373   return out;
00374 err:
00375   if (stream) {
00376     inflateEnd(stream);
00377     delete stream;
00378   }
00379   if (errmsg)
00380     *errmsg = errorstr;
00381   return QByteArray();
00382 }
00383 
Generated on Mon Aug 30 23:09:51 2010 for Vidalia by  doxygen 1.6.3