zlibbytearray.cpp

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

Generated on Wed Sep 5 15:49:28 2007 for Vidalia by  doxygen 1.5.3