Vidalia  0.3.1
ZlibByteArray.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 **
10 ** * * *
11 **
12 ** Zlib support in this class is derived from Tor's torgzip.[ch].
13 ** Tor is distributed under this license:
14 **
15 ** Copyright (c) 2001-2004, Roger Dingledine
16 ** Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
17 **
18 ** Redistribution and use in source and binary forms, with or without
19 ** modification, are permitted provided that the following conditions are
20 ** met:
21 **
22 ** * Redistributions of source code must retain the above copyright
23 ** notice, this list of conditions and the following disclaimer.
24 **
25 ** * Redistributions in binary form must reproduce the above
26 ** copyright notice, this list of conditions and the following disclaimer
27 ** in the documentation and/or other materials provided with the
28 ** distribution.
29 **
30 ** * Neither the names of the copyright owners nor the names of its
31 ** contributors may be used to endorse or promote products derived from
32 ** this software without specific prior written permission.
33 **
34 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
35 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
36 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
37 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
38 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
40 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
41 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
42 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
43 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
44 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45 */
46 
47 /*
48 ** \file ZlibByteArray.cpp
49 ** \brief Wrapper around QByteArray that adds compression capabilities
50 */
51 
52 #include "config.h"
53 
54 #include <QString>
55 
56 #ifdef HAVE_LIMITS_H
57 #include <limits.h>
58 #elif defined(HAVE_SYS_LIMITS_H)
59 #include <sys/limits.h>
60 #endif
61 
62 /* The following check for UINT_MAX is derived from Tor's torint.h. See
63  * the top of this file for details on Tor's license. */
64 #ifndef UINT_MAX
65 #if (SIZEOF_INT == 2)
66 #define UINT_MAX 0xffffu
67 #elif (SIZEOF_INT == 4)
68 #define UINT_MAX 0xffffffffu
69 #elif (SIZEOF_INT == 8)
70 #define UINT_MAX 0xffffffffffffffffu
71 #else
72 #error "Your platform uses a sizeof(int) that we don't understand."
73 #endif
74 #endif
75 
76 #include "zlib.h"
77 #include <ZlibByteArray.h>
78 
79 
80 /** Constructor */
82 : QByteArray(data)
83 {
84 }
85 
86 /** Return the 'bits' value to tell zlib to use <b>method</b>.*/
87 int
89 {
90  /* Bits+16 means "use gzip" in zlib >= 1.2 */
91  return (method == Gzip ? 15+16 : 15);
92 }
93 
94 /** Returns a string description of <b>method</b>. */
95 QString
97 {
98  switch (method) {
99  case None: return "None";
100  case Zlib: return "Zlib";
101  case Gzip: return "Gzip";
102  default: return "Unknown";
103  }
104 }
105 
106 /** Returns true if the Zlib compression library is available and usable. */
107 bool
109 {
110  static int isZlibAvailable = -1;
111  if (isZlibAvailable >= 0)
112  return isZlibAvailable;
113 
114  /* From zlib.h:
115  * "The application can compare zlibVersion and ZLIB_VERSION for consistency.
116  * If the first character differs, the library code actually used is
117  * not compatible with the zlib.h header file used by the application." */
118  QString libVersion(zlibVersion());
119  QString headerVersion(ZLIB_VERSION);
120  if (libVersion.isEmpty() || headerVersion.isEmpty() ||
121  libVersion.at(0) != headerVersion.at(0))
122  isZlibAvailable = 0;
123  else
124  isZlibAvailable = 1;
125 
126  return isZlibAvailable;
127 }
128 
129 /** Returns true iff we support gzip-based compression. Otherwise, we need to
130  * use zlib. */
131 bool
133 {
134  static int isGzipSupported = -1;
135  if (isGzipSupported >= 0)
136  return isGzipSupported;
137 
138  QString version(zlibVersion());
139  if (version.startsWith("0.") ||
140  version.startsWith("1.0") ||
141  version.startsWith("1.1"))
142  isGzipSupported = 0;
143  else
144  isGzipSupported = 1;
145 
146  return isGzipSupported;
147 }
148 
149 /** Compresses the current contents of this object using <b>method</b>.
150  * Returns the compressed data if successful. If an error occurs, this will
151  * return an empty QByteArray and set the optional <b>errmsg</b> to a string
152  * describing the failure. */
153 QByteArray
155  QString *errmsg) const
156 {
157  return compress(QByteArray(data()), method, errmsg);
158 }
159 
160 /** Compresses <b>in</b> using <b>method</b>. Returns the compressed data
161  * if successful. If an error occurs, this will return an empty QByteArray and
162  * set the optional <b>errmsg</b> to a string describing the failure. */
163 QByteArray
164 ZlibByteArray::compress(const QByteArray in,
165  const CompressionMethod method,
166  QString *errmsg)
167 {
168  QByteArray out;
169  QString errorstr;
170  struct z_stream_s *stream = NULL;
171  size_t out_size;
172  size_t out_len;
173  size_t in_len = in.length();
174  off_t offset;
175 
176  if (method == None)
177  return in;
178  if (method == Gzip && !isGzipSupported()) {
179  /* Old zlib versions don't support gzip in deflateInit2 */
180  if (errmsg)
181  *errmsg = QString("Gzip not supported with zlib %1")
182  .arg(ZLIB_VERSION);
183  return QByteArray();
184  }
185 
186  stream = new struct z_stream_s;
187  stream->zalloc = Z_NULL;
188  stream->zfree = Z_NULL;
189  stream->opaque = NULL;
190  stream->next_in = (unsigned char*)in.data();
191  stream->avail_in = in_len;
192 
193  if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED,
194  methodBits(method),
195  8, Z_DEFAULT_STRATEGY) != Z_OK) {
196  errorstr = QString("Error from deflateInit2: %1")
197  .arg(stream->msg ? stream->msg : "<no message>");
198  goto err;
199  }
200 
201  /* Guess 50% compression. */
202  out_size = in_len / 2;
203  if (out_size < 1024) out_size = 1024;
204 
205  out.resize(out_size);
206  stream->next_out = (unsigned char*)out.data();
207  stream->avail_out = out_size;
208 
209  while (1) {
210  switch (deflate(stream, Z_FINISH))
211  {
212  case Z_STREAM_END:
213  goto done;
214  case Z_OK:
215  /* In case zlib doesn't work as I think .... */
216  if (stream->avail_out >= stream->avail_in+16)
217  break;
218  case Z_BUF_ERROR:
219  offset = stream->next_out - ((unsigned char*)out.data());
220  out_size *= 2;
221  out.resize(out_size);
222  stream->next_out = (unsigned char*)(out.data() + offset);
223  if (out_size - offset > UINT_MAX) {
224  errorstr =
225  "Ran over unsigned int limit of zlib while uncompressing";
226  goto err;
227  }
228  stream->avail_out = (unsigned int)(out_size - offset);
229  break;
230  default:
231  errorstr = QString("%1 compression didn't finish: %2")
232  .arg(methodString(method))
233  .arg(stream->msg ? stream->msg : "<no message>");
234  goto err;
235  }
236  }
237 done:
238  out_len = stream->total_out;
239  if (deflateEnd(stream)!=Z_OK) {
240  errorstr = "Error freeing zlib structures";
241  goto err;
242  }
243  out.resize(out_len);
244  delete stream;
245  return out;
246 err:
247  if (stream) {
248  deflateEnd(stream);
249  delete stream;
250  }
251  if (errmsg)
252  *errmsg = errorstr;
253  return QByteArray();
254 }
255 
256 /** Uncompresses the current contents of this object using <b>method</b>.
257  * Returns the uncompressed data if successful. If an error occurs, this will
258  * return an empty QByteArray and set the optional <b>errmsg</b> to a string
259  * describing the failure. */
260 QByteArray
262  QString *errmsg) const
263 {
264  return uncompress(QByteArray(data()), method, errmsg);
265 }
266 
267 /** Uncompresses <b>in</b> using <b>method</b>. Returns the uncompressed data
268  * if successful. If an error occurs, this will return an empty QByteArray and
269  * set the optional <b>errmsg</b> to a string describing the failure. */
270 QByteArray
271 ZlibByteArray::uncompress(const QByteArray in,
272  const CompressionMethod method,
273  QString *errmsg)
274 {
275  QByteArray out;
276  QString errorstr;
277  struct z_stream_s *stream = NULL;
278  size_t out_size;
279  size_t out_len;
280  size_t in_len = in.length();
281  off_t offset;
282  int r;
283 
284  if (method == None)
285  return in;
286  if (method == Gzip && !isGzipSupported()) {
287  /* Old zlib versions don't support gzip in inflateInit2 */
288  if (errmsg)
289  *errmsg = QString("Gzip not supported with zlib %1")
290  .arg(ZLIB_VERSION);
291  return QByteArray();
292  }
293 
294  stream = new struct z_stream_s;
295  stream->zalloc = Z_NULL;
296  stream->zfree = Z_NULL;
297  stream->opaque = NULL;
298  stream->msg = NULL;
299  stream->next_in = (unsigned char*) in.data();
300  stream->avail_in = in_len;
301 
302  if (inflateInit2(stream,
303  methodBits(method)) != Z_OK) {
304  errorstr = QString("Error from inflateInit2: %1")
305  .arg(stream->msg ? stream->msg : "<no message>");
306  goto err;
307  }
308 
309  out_size = in_len * 2; /* guess 50% compression. */
310  if (out_size < 1024) out_size = 1024;
311 
312  out.resize(out_size);
313  stream->next_out = (unsigned char*)out.data();
314  stream->avail_out = out_size;
315 
316  while (1) {
317  switch (inflate(stream, Z_FINISH))
318  {
319  case Z_STREAM_END:
320  if (stream->avail_in == 0)
321  goto done;
322  /* There may be more compressed data here. */
323  if ((r = inflateEnd(stream)) != Z_OK) {
324  errorstr = "Error freeing zlib structures";
325  goto err;
326  }
327  if (inflateInit2(stream, methodBits(method)) != Z_OK) {
328  errorstr = QString("Error from second inflateInit2: %1")
329  .arg(stream->msg ? stream->msg : "<no message>");
330  goto err;
331  }
332  break;
333  case Z_OK:
334  if (stream->avail_in == 0)
335  goto done;
336  /* In case zlib doesn't work as I think.... */
337  if (stream->avail_out >= stream->avail_in+16)
338  break;
339  case Z_BUF_ERROR:
340  if (stream->avail_out > 0) {
341  errorstr = QString("Possible truncated or corrupt %1 data")
342  .arg(methodString(method));
343  goto err;
344  }
345  offset = stream->next_out - (unsigned char*)out.data();
346  out_size *= 2;
347  out.resize(out_size);
348  stream->next_out = (unsigned char*)(out.data() + offset);
349  if (out_size - offset > UINT_MAX) {
350  errorstr =
351  "Ran over unsigned int limit of zlib while uncompressing";
352  goto err;
353  }
354  stream->avail_out = (unsigned int)(out_size - offset);
355  break;
356  default:
357  errorstr = QString("%1 decompression returned an error: %2")
358  .arg(methodString(method))
359  .arg(stream->msg ? stream->msg : "<no message>");
360  goto err;
361  }
362  }
363 done:
364  out_len = stream->next_out - (unsigned char*)out.data();
365  r = inflateEnd(stream);
366  delete stream;
367  if (r != Z_OK) {
368  errorstr = "Error freeing zlib structure";
369  goto err;
370  }
371  out.resize(out_len);
372  return out;
373 err:
374  if (stream) {
375  inflateEnd(stream);
376  delete stream;
377  }
378  if (errmsg)
379  *errmsg = errorstr;
380  return QByteArray();
381 }
382