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