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>.*/
87int
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>. */
95QString
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. */
107bool
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. */
131bool
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. */
153QByteArray
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. */
163QByteArray
164ZlibByteArray::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 }
237done:
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;
246err:
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. */
260QByteArray
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. */
270QByteArray
271ZlibByteArray::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 }
363done:
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;
373err:
374 if (stream) {
375 inflateEnd(stream);
376 delete stream;
377 }
378 if (errmsg)
379 *errmsg = errorstr;
380 return QByteArray();
381}
382
QByteArray compress(const CompressionMethod method=Zlib, QString *errmsg=0) const
static bool isZlibAvailable()
static QString methodString(CompressionMethod method)
static bool isGzipSupported()
ZlibByteArray(QByteArray data)
QByteArray uncompress(CompressionMethod method=Zlib, QString *errmsg=0) const
static int methodBits(CompressionMethod method)
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37