1 /*
2 *
3 * Copyright (C) 1994-2002, OFFIS
4 *
5 * This software and supporting documentation were developed by
6 *
7 * Kuratorium OFFIS e.V.
8 * Healthcare Information and Communication Systems
9 * Escherweg 2
10 * D-26121 Oldenburg, Germany
11 *
12 * THIS SOFTWARE IS MADE AVAILABLE, AS IS, AND OFFIS MAKES NO WARRANTY
13 * REGARDING THE SOFTWARE, ITS PERFORMANCE, ITS MERCHANTABILITY OR
14 * FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER DISEASES OR
15 * ITS CONFORMITY TO ANY SPECIFICATION. THE ENTIRE RISK AS TO QUALITY AND
16 * PERFORMANCE OF THE SOFTWARE IS WITH THE USER.
17 *
18 * Module: dcmdata
19 *
20 * Author: Marco Eichelberg
21 *
22 * Purpose: zlib compression filter for input streams
23 *
24 */
25
26 #include "osconfig.h"
27
28 #ifdef WITH_ZLIB
29
30 #include "dcistrmz.h"
31 #include "dcerror.h"
32 #include "ofconsol.h"
33
34 #define DCMZLIBINPUTFILTER_BUFSIZE 4096
35 #define DCMZLIBINPUTFILTER_PUTBACKSIZE 1024
36
37 OFGlobal<OFBool> dcmZlibExpectRFC1950Encoding(OFFalse);
38
39
DcmZLibInputFilter()40 DcmZLibInputFilter::DcmZLibInputFilter()
41 : DcmInputFilter()
42 , current_(NULL)
43 , zstream_(new z_stream)
44 , status_(EC_MemoryExhausted)
45 , eos_(OFFalse)
46 , inputBuf_(new unsigned char[DCMZLIBINPUTFILTER_BUFSIZE])
47 , inputBufStart_(0)
48 , inputBufCount_(0)
49 , outputBuf_(new unsigned char[DCMZLIBINPUTFILTER_BUFSIZE])
50 , outputBufStart_(0)
51 , outputBufCount_(0)
52 , outputBufPutback_(0)
53 , padded_(OFFalse)
54 {
55 if (zstream_ && inputBuf_ && outputBuf_)
56 {
57 zstream_->zalloc = Z_NULL;
58 zstream_->zfree = Z_NULL;
59 zstream_->opaque = Z_NULL;
60 zstream_->next_in = Z_NULL;
61 zstream_->avail_in = 0;
62
63 if (dcmZlibExpectRFC1950Encoding.get())
64 {
65 /* expect non-standard bitstream *with* zlib header
66 * This is easy because it is the normal zlib format anyway.
67 */
68 if (Z_OK == inflateInit(zstream_)) status_ = EC_Normal;
69 else
70 {
71 OFString etext = "ZLib Error: ";
72 if (zstream_->msg) etext += zstream_->msg;
73 status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str());
74 }
75 }
76 else
77 {
78 /* windowBits is passed < 0 to tell that there is no zlib header.
79 * Note that in this case inflate *requires* an extra "dummy" byte
80 * after the compressed stream in order to complete decompression and
81 * return Z_STREAM_END.
82 */
83 if (Z_OK == inflateInit2(zstream_, -MAX_WBITS)) status_ = EC_Normal;
84 else
85 {
86 OFString etext = "ZLib Error: ";
87 if (zstream_->msg) etext += zstream_->msg;
88 status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str());
89 }
90 }
91 }
92 }
93
~DcmZLibInputFilter()94 DcmZLibInputFilter::~DcmZLibInputFilter()
95 {
96 if (zstream_)
97 {
98 inflateEnd(zstream_); // discards any unprocessed input and does not flush any pending output
99 delete zstream_;
100 }
101 delete[] inputBuf_;
102 delete[] outputBuf_;
103 }
104
good() const105 OFBool DcmZLibInputFilter::good() const
106 {
107 return status_.good();
108 }
109
status() const110 OFCondition DcmZLibInputFilter::status() const
111 {
112 return status_;
113 }
114
eos() const115 OFBool DcmZLibInputFilter::eos() const
116 {
117 if (status_.bad() || (current_ == NULL)) return OFTrue;
118
119 // there may be trailing garbage after the compressed stream.
120 // we report eos if the decompressor has reported Z_STREAM_END
121 // and the output buffer is empty.
122 return (outputBufCount_ == 0) && eos_;
123 }
124
avail() const125 Uint32 DcmZLibInputFilter::avail() const
126 {
127 if (status_.good()) return outputBufCount_; else return 0;
128 }
129
read(void * buf,Uint32 buflen)130 Uint32 DcmZLibInputFilter::read(void *buf, Uint32 buflen)
131 {
132 if (status_.bad() || (current_ == NULL) || (buf == NULL)) return 0;
133 unsigned char *target = (unsigned char *)buf;
134 Uint32 offset = 0;
135 Uint32 availBytes = 0;
136 Uint32 result = 0;
137 do
138 {
139 // copy bytes from output buffer to user provided block of data
140 if (outputBufCount_)
141 {
142 // determine next block of data in output buffer
143 offset = outputBufStart_ + outputBufPutback_;
144 if (offset >= DCMZLIBINPUTFILTER_BUFSIZE) offset -= DCMZLIBINPUTFILTER_BUFSIZE;
145
146 availBytes = outputBufCount_;
147 if (offset + availBytes > DCMZLIBINPUTFILTER_BUFSIZE) availBytes = DCMZLIBINPUTFILTER_BUFSIZE - offset;
148 if (availBytes > buflen) availBytes = buflen;
149
150 if (availBytes) memcpy(target, outputBuf_ + offset, (size_t)availBytes);
151 target += availBytes;
152 result += availBytes;
153 buflen -= availBytes;
154
155 // adjust pointers
156 outputBufPutback_ += availBytes;
157 outputBufCount_ -= availBytes;
158 if (outputBufPutback_ > DCMZLIBINPUTFILTER_PUTBACKSIZE)
159 {
160 outputBufStart_ += outputBufPutback_ - DCMZLIBINPUTFILTER_PUTBACKSIZE;
161 if (outputBufStart_ >= DCMZLIBINPUTFILTER_BUFSIZE) outputBufStart_ -= DCMZLIBINPUTFILTER_BUFSIZE;
162 outputBufPutback_ = DCMZLIBINPUTFILTER_PUTBACKSIZE;
163 }
164 }
165
166 // refill output buffer
167 fillOutputBuffer();
168 } while (buflen && outputBufCount_);
169
170 // we're either done or the output buffer is empty because of producer suspension
171 return result;
172 }
173
skip(Uint32 skiplen)174 Uint32 DcmZLibInputFilter::skip(Uint32 skiplen)
175 {
176 if (status_.bad() || (current_ == NULL)) return 0;
177 Uint32 offset = 0;
178 Uint32 availBytes = 0;
179 Uint32 result = 0;
180 do
181 {
182 // copy bytes from output buffer to user provided block of data
183 if (outputBufCount_)
184 {
185 // determine next block of data in output buffer
186 offset = outputBufStart_ + outputBufPutback_;
187 if (offset >= DCMZLIBINPUTFILTER_BUFSIZE) offset -= DCMZLIBINPUTFILTER_BUFSIZE;
188
189 availBytes = outputBufCount_;
190 if (offset + availBytes > DCMZLIBINPUTFILTER_BUFSIZE) availBytes = DCMZLIBINPUTFILTER_BUFSIZE - offset;
191 if (availBytes > skiplen) availBytes = skiplen;
192 result += availBytes;
193 skiplen -= availBytes;
194
195 // adjust pointers
196 outputBufPutback_ += availBytes;
197 outputBufCount_ -= availBytes;
198 if (outputBufPutback_ > DCMZLIBINPUTFILTER_PUTBACKSIZE)
199 {
200 outputBufStart_ += outputBufPutback_ - DCMZLIBINPUTFILTER_PUTBACKSIZE;
201 outputBufPutback_ = DCMZLIBINPUTFILTER_PUTBACKSIZE;
202 if (outputBufStart_ >= DCMZLIBINPUTFILTER_BUFSIZE) outputBufStart_ -= DCMZLIBINPUTFILTER_BUFSIZE;
203 }
204 }
205
206 // refill output buffer
207 fillOutputBuffer();
208 } while (skiplen && outputBufCount_);
209
210 // we're either done or the output buffer is empty because of producer suspension
211 return result;
212 }
213
putback(Uint32 num)214 void DcmZLibInputFilter::putback(Uint32 num)
215 {
216 if (num > outputBufPutback_) status_ = EC_PutbackFailed;
217 else
218 {
219 outputBufPutback_ -= num;
220 outputBufCount_ += num;
221 }
222 }
223
append(DcmProducer & producer)224 void DcmZLibInputFilter::append(DcmProducer& producer)
225 {
226 current_ = &producer;
227 }
228
fillInputBuffer()229 Uint32 DcmZLibInputFilter::fillInputBuffer()
230 {
231 Uint32 result = 0;
232 if (status_.good() && current_ && (inputBufCount_ < DCMZLIBINPUTFILTER_BUFSIZE))
233 {
234 // use first part of input buffer
235 if (inputBufStart_ + inputBufCount_ < DCMZLIBINPUTFILTER_BUFSIZE)
236 {
237 result = current_->read(inputBuf_ + inputBufStart_ + inputBufCount_,
238 DCMZLIBINPUTFILTER_BUFSIZE - (inputBufStart_ + inputBufCount_));
239
240 inputBufCount_ += result;
241
242 if (result == 0)
243 {
244 if (current_->eos() && !padded_)
245 {
246 // producer has signalled eos, now append zero pad byte that makes
247 // zlib recognize the end of stream when no zlib header is present
248 *(inputBuf_ + inputBufStart_ + inputBufCount_) = 0;
249 inputBufCount_++;
250 padded_ = OFTrue;
251 }
252 return result; // producer suspension
253 }
254 }
255
256 // use second part of input buffer
257 if (inputBufCount_ < DCMZLIBINPUTFILTER_BUFSIZE &&
258 inputBufStart_ + inputBufCount_ >= DCMZLIBINPUTFILTER_BUFSIZE)
259 {
260 Uint32 result2 = current_->read(inputBuf_ + (inputBufStart_ + inputBufCount_ - DCMZLIBINPUTFILTER_BUFSIZE),
261 DCMZLIBINPUTFILTER_BUFSIZE - inputBufCount_);
262
263 inputBufCount_ += result2;
264 result += result2;
265
266 if (result2 == 0 && current_->eos() && !padded_)
267 {
268 // producer has signalled eos, now append zero pad byte that makes
269 // zlib recognize the end of stream when no zlib header is present
270 *(inputBuf_ + inputBufStart_ + inputBufCount_ - DCMZLIBINPUTFILTER_BUFSIZE) = 0;
271 inputBufCount_++;
272 padded_ = OFTrue;
273 }
274 }
275 }
276 return result;
277 }
278
279
decompress(const void * buf,Uint32 buflen)280 Uint32 DcmZLibInputFilter::decompress(const void *buf, Uint32 buflen)
281 {
282 Uint32 result = 0;
283
284 zstream_->next_out = (Bytef *)buf;
285 zstream_->avail_out = (uInt)buflen;
286 int astatus;
287
288 // decompress from inputBufStart_ to end of data or end of buffer, whatever comes first
289 Uint32 numBytes = (inputBufStart_ + inputBufCount_ > DCMZLIBINPUTFILTER_BUFSIZE) ?
290 (DCMZLIBINPUTFILTER_BUFSIZE - inputBufStart_) : inputBufCount_ ;
291
292 if (numBytes || buflen)
293 {
294 zstream_->next_in = (Bytef *)(inputBuf_ + inputBufStart_);
295 zstream_->avail_in = (uInt) numBytes;
296 astatus = inflate(zstream_, 0);
297
298 if (astatus == Z_OK || astatus == Z_BUF_ERROR) { /* everything OK */ }
299 else if (astatus == Z_STREAM_END)
300 {
301 #ifdef DEBUG
302 if (!eos_)
303 {
304 Uint32 count = inputBufCount_ - (numBytes - (Uint32)(zstream_->avail_in));
305 if (count > 2)
306 {
307 /* we silently ignore up to two trailing bytes after the end of the
308 * deflated stream. One byte has been added by ourselves to make sure
309 * zlib detects eos, another one might be the padding necessary for
310 * odd length zlib streams transmitted through a DICOM network
311 * (where PDVs always have even length).
312 * Everything else generates a warning.
313 */
314 ofConsole.lockCerr() << "zlib: " << count-1 << " pending input bytes in buffer." << endl;
315 ofConsole.unlockCerr();
316 }
317 }
318 #endif
319 eos_ = OFTrue;
320 }
321 else
322 {
323 OFString etext = "ZLib Error: ";
324 if (zstream_->msg) etext += zstream_->msg;
325 status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str());
326 }
327
328 // adjust counters
329 inputBufStart_ += numBytes - (Uint32)(zstream_->avail_in);
330 inputBufCount_ -= numBytes - (Uint32)(zstream_->avail_in);
331
332 if (inputBufStart_ == DCMZLIBINPUTFILTER_BUFSIZE)
333 {
334 // wrapped around
335 inputBufStart_ = 0;
336
337 // now flush to end of data
338 if (inputBufCount_ && (zstream_->avail_out > 0))
339 {
340 zstream_->next_in = (Bytef *)inputBuf_;
341 zstream_->avail_in = (uInt) inputBufCount_;
342 astatus = inflate(zstream_, 0);
343
344 if (astatus == Z_OK || astatus == Z_BUF_ERROR) { /* everything OK */ }
345 else if (astatus == Z_STREAM_END)
346 {
347 #ifdef DEBUG
348 if (!eos_)
349 {
350 Uint32 count = (Uint32)(zstream_->avail_in);
351 if (count > 2)
352 {
353 /* we silently ignore up to two trailing bytes after the end of the
354 * deflated stream. One byte has been added by ourselves to make sure
355 * zlib detects eos, another one might be the padding necessary for
356 * odd length zlib streams transmitted through a DICOM network
357 * (where PDVs always have even length).
358 * Everything else generates a warning.
359 */
360 ofConsole.lockCerr() << "zlib: " << count-1 << " pending input bytes in buffer." << endl;
361 ofConsole.unlockCerr();
362 }
363 }
364 #endif
365 eos_ = OFTrue;
366 }
367 else
368 {
369 OFString etext = "ZLib Error: ";
370 if (zstream_->msg) etext += zstream_->msg;
371 status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str());
372 }
373
374 // adjust counters
375 inputBufStart_ += inputBufCount_ - (Uint32)(zstream_->avail_in);
376 inputBufCount_ = (Uint32)(zstream_->avail_in);
377 }
378 }
379
380 // reset buffer start to make things faster
381 if (inputBufCount_ == 0) inputBufStart_ = 0;
382
383 // compute result
384 result = buflen - (Uint32)(zstream_->avail_out);
385 }
386 return result;
387 }
388
fillOutputBuffer()389 void DcmZLibInputFilter::fillOutputBuffer()
390 {
391 Uint32 inputBytes = 0;
392 Uint32 outputBytes = 0;
393 Uint32 offset = 0;
394 Uint32 availBytes = 0;
395 do
396 {
397 inputBytes = fillInputBuffer();
398
399 // determine next block of free space in output buffer
400 offset = outputBufStart_ + outputBufPutback_ + outputBufCount_;
401 if (offset >= DCMZLIBINPUTFILTER_BUFSIZE) offset -= DCMZLIBINPUTFILTER_BUFSIZE;
402
403 availBytes = DCMZLIBINPUTFILTER_BUFSIZE - (outputBufPutback_ + outputBufCount_);
404 if (offset + availBytes > DCMZLIBINPUTFILTER_BUFSIZE) availBytes = DCMZLIBINPUTFILTER_BUFSIZE - offset;
405
406 // decompress to output buffer
407 outputBytes = decompress(outputBuf_ + offset, availBytes);
408 outputBufCount_ += outputBytes;
409 }
410 while (inputBytes || outputBytes);
411 }
412
413 #else /* WITH_ZLIB */
414
415 /* make sure that the object file is not completely empty if compiled
416 * without zlib because some linkers might fail otherwise.
417 */
dcistrmz_dummy_function()418 void dcistrmz_dummy_function()
419 {
420 return;
421 }
422
423 #endif /* WITH_ZLIB */
424