Backout bug 449422.
[wine-gecko.git] / netwerk / streamconv / converters / nsHTTPCompressConv.cpp
blobc873b482bcf2b5bf2330ba9854393a63ab4738a2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 cindent et: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * David Dick <ddick@cpan.org>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsHTTPCompressConv.h"
41 #include "nsMemory.h"
42 #include "plstr.h"
43 #include "prlog.h"
44 #include "nsIChannel.h"
45 #include "nsCOMPtr.h"
46 #include "nsReadableUtils.h"
47 #include "nsNetError.h"
48 #include "nsStreamUtils.h"
49 #include "nsStringStream.h"
50 #include "nsComponentManagerUtils.h"
52 // nsISupports implementation
53 NS_IMPL_ISUPPORTS3(nsHTTPCompressConv,
54 nsIStreamConverter,
55 nsIStreamListener,
56 nsIRequestObserver)
58 // nsFTPDirListingConv methods
59 nsHTTPCompressConv::nsHTTPCompressConv()
60 : mListener(nsnull)
61 , mMode(HTTP_COMPRESS_IDENTITY)
62 , mOutBuffer(NULL)
63 , mInpBuffer(NULL)
64 , mOutBufferLen(0)
65 , mInpBufferLen(0)
66 , mCheckHeaderDone(PR_FALSE)
67 , mStreamEnded(PR_FALSE)
68 , mStreamInitialized(PR_FALSE)
69 , mLen(0)
70 , hMode(0)
71 , mSkipCount(0)
72 , mFlags(0)
76 nsHTTPCompressConv::~nsHTTPCompressConv()
78 NS_IF_RELEASE(mListener);
80 if (mInpBuffer)
81 nsMemory::Free(mInpBuffer);
83 if (mOutBuffer)
84 nsMemory::Free(mOutBuffer);
86 // For some reason we are not getting Z_STREAM_END. But this was also seen
87 // for mozilla bug 198133. Need to handle this case.
88 if ((mStreamInitialized == PR_TRUE) && (mStreamEnded == PR_FALSE))
89 inflateEnd (&d_stream);
92 NS_IMETHODIMP
93 nsHTTPCompressConv::AsyncConvertData(const char *aFromType,
94 const char *aToType,
95 nsIStreamListener *aListener,
96 nsISupports *aCtxt)
98 if (!PL_strncasecmp(aFromType, HTTP_COMPRESS_TYPE, sizeof(HTTP_COMPRESS_TYPE)-1) ||
99 !PL_strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE, sizeof(HTTP_X_COMPRESS_TYPE)-1))
100 mMode = HTTP_COMPRESS_COMPRESS;
102 else if (!PL_strncasecmp(aFromType, HTTP_GZIP_TYPE, sizeof(HTTP_GZIP_TYPE)-1) ||
103 !PL_strncasecmp(aFromType, HTTP_X_GZIP_TYPE, sizeof(HTTP_X_GZIP_TYPE)-1))
104 mMode = HTTP_COMPRESS_GZIP;
106 else if (!PL_strncasecmp(aFromType, HTTP_DEFLATE_TYPE, sizeof(HTTP_DEFLATE_TYPE)-1))
107 mMode = HTTP_COMPRESS_DEFLATE;
109 // hook ourself up with the receiving listener.
110 mListener = aListener;
111 NS_ADDREF(mListener);
113 mAsyncConvContext = aCtxt;
114 return NS_OK;
117 NS_IMETHODIMP
118 nsHTTPCompressConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
120 return mListener->OnStartRequest(request, aContext);
123 NS_IMETHODIMP
124 nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
125 nsresult aStatus)
127 return mListener->OnStopRequest(request, aContext, aStatus);
130 NS_IMETHODIMP
131 nsHTTPCompressConv::OnDataAvailable(nsIRequest* request,
132 nsISupports *aContext,
133 nsIInputStream *iStr,
134 PRUint32 aSourceOffset,
135 PRUint32 aCount)
137 nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING;
138 PRUint32 streamLen = aCount;
140 if (streamLen == 0)
142 NS_ERROR("count of zero passed to OnDataAvailable");
143 return NS_ERROR_UNEXPECTED;
146 if (mStreamEnded)
148 // Hmm... this may just indicate that the data stream is done and that
149 // what's left is either metadata or padding of some sort.... throwing
150 // it out is probably the safe thing to do.
151 PRUint32 n;
152 return iStr->ReadSegments(NS_DiscardSegment, nsnull, streamLen, &n);
155 switch (mMode)
157 case HTTP_COMPRESS_GZIP:
158 streamLen = check_header(iStr, streamLen, &rv);
160 if (rv != NS_OK)
161 return rv;
163 if (streamLen == 0)
164 return NS_OK;
166 case HTTP_COMPRESS_DEFLATE:
168 if (mInpBuffer != NULL && streamLen > mInpBufferLen)
170 mInpBuffer = (unsigned char *) nsMemory::Realloc(mInpBuffer, mInpBufferLen = streamLen);
172 if (mOutBufferLen < streamLen * 2)
173 mOutBuffer = (unsigned char *) nsMemory::Realloc(mOutBuffer, mOutBufferLen = streamLen * 3);
175 if (mInpBuffer == NULL || mOutBuffer == NULL)
176 return NS_ERROR_OUT_OF_MEMORY;
179 if (mInpBuffer == NULL)
180 mInpBuffer = (unsigned char *) nsMemory::Alloc(mInpBufferLen = streamLen);
182 if (mOutBuffer == NULL)
183 mOutBuffer = (unsigned char *) nsMemory::Alloc(mOutBufferLen = streamLen * 3);
185 if (mInpBuffer == NULL || mOutBuffer == NULL)
186 return NS_ERROR_OUT_OF_MEMORY;
188 iStr->Read((char *)mInpBuffer, streamLen, &rv);
190 if (NS_FAILED(rv))
191 return rv;
193 if (mMode == HTTP_COMPRESS_DEFLATE)
195 if (!mStreamInitialized)
197 memset(&d_stream, 0, sizeof (d_stream));
199 if (inflateInit(&d_stream) != Z_OK)
200 return NS_ERROR_FAILURE;
202 mStreamInitialized = PR_TRUE;
204 d_stream.next_in = mInpBuffer;
205 d_stream.avail_in = (uInt)streamLen;
207 mDummyStreamInitialised = PR_FALSE;
208 for (;;)
210 d_stream.next_out = mOutBuffer;
211 d_stream.avail_out = (uInt)mOutBufferLen;
213 int code = inflate(&d_stream, Z_NO_FLUSH);
214 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
216 if (code == Z_STREAM_END)
218 if (bytesWritten)
220 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
221 if (NS_FAILED (rv))
222 return rv;
225 inflateEnd(&d_stream);
226 mStreamEnded = PR_TRUE;
227 break;
229 else if (code == Z_OK)
231 if (bytesWritten)
233 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
234 if (NS_FAILED (rv))
235 return rv;
238 else if (code == Z_BUF_ERROR)
240 if (bytesWritten)
242 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
243 if (NS_FAILED (rv))
244 return rv;
246 break;
248 else if (code == Z_DATA_ERROR)
250 // some servers (notably Apache with mod_deflate) don't generate zlib headers
251 // insert a dummy header and try again
252 static char dummy_head[2] =
254 0x8 + 0x7 * 0x10,
255 (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
257 inflateReset(&d_stream);
258 d_stream.next_in = (Bytef*) dummy_head;
259 d_stream.avail_in = sizeof(dummy_head);
261 code = inflate(&d_stream, Z_NO_FLUSH);
262 if (code != Z_OK)
263 return NS_ERROR_FAILURE;
265 // stop an endless loop caused by non-deflate data being labelled as deflate
266 if (mDummyStreamInitialised) {
267 NS_ERROR("endless loop detected");
268 return NS_ERROR_INVALID_CONTENT_ENCODING;
270 mDummyStreamInitialised = PR_TRUE;
271 // reset stream pointers to our original data
272 d_stream.next_in = mInpBuffer;
273 d_stream.avail_in = (uInt)streamLen;
275 else
276 return NS_ERROR_INVALID_CONTENT_ENCODING;
277 } /* for */
279 else
281 if (!mStreamInitialized)
283 memset(&d_stream, 0, sizeof (d_stream));
285 if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK)
286 return NS_ERROR_FAILURE;
288 mStreamInitialized = PR_TRUE;
291 d_stream.next_in = mInpBuffer;
292 d_stream.avail_in = (uInt)streamLen;
294 for (;;)
296 d_stream.next_out = mOutBuffer;
297 d_stream.avail_out = (uInt)mOutBufferLen;
299 int code = inflate (&d_stream, Z_NO_FLUSH);
300 unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
302 if (code == Z_STREAM_END)
304 if (bytesWritten)
306 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
307 if (NS_FAILED (rv))
308 return rv;
311 inflateEnd(&d_stream);
312 mStreamEnded = PR_TRUE;
313 break;
315 else if (code == Z_OK)
317 if (bytesWritten)
319 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
320 if (NS_FAILED (rv))
321 return rv;
324 else if (code == Z_BUF_ERROR)
326 if (bytesWritten)
328 rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
329 if (NS_FAILED (rv))
330 return rv;
332 break;
334 else
335 return NS_ERROR_INVALID_CONTENT_ENCODING;
336 } /* for */
337 } /* gzip */
338 break;
340 default:
341 rv = mListener->OnDataAvailable(request, aContext, iStr, aSourceOffset, aCount);
342 if (NS_FAILED (rv))
343 return rv;
344 } /* switch */
346 return NS_OK;
347 } /* OnDataAvailable */
350 // XXX/ruslan: need to implement this too
352 NS_IMETHODIMP
353 nsHTTPCompressConv::Convert(nsIInputStream *aFromStream,
354 const char *aFromType,
355 const char *aToType,
356 nsISupports *aCtxt,
357 nsIInputStream **_retval)
359 return NS_ERROR_NOT_IMPLEMENTED;
362 nsresult
363 nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request,
364 nsISupports *context, PRUint32 offset,
365 const char *buffer, PRUint32 count)
367 if (!mStream) {
368 mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
369 NS_ENSURE_STATE(mStream);
372 mStream->ShareData(buffer, count);
374 nsresult rv = mListener->OnDataAvailable(request, context, mStream,
375 offset, count);
377 // Make sure the stream no longer references |buffer| in case our listener
378 // is crazy enough to try to read from |mStream| after ODA.
379 mStream->ShareData("", 0);
381 return rv;
384 #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
385 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
386 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
387 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
388 #define COMMENT 0x10 /* bit 4 set: file comment present */
389 #define RESERVED 0xE0 /* bits 5..7: reserved */
391 static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
393 PRUint32
394 nsHTTPCompressConv::check_header(nsIInputStream *iStr, PRUint32 streamLen, nsresult *rs)
396 nsresult rv;
397 enum { GZIP_INIT = 0, GZIP_OS, GZIP_EXTRA0, GZIP_EXTRA1, GZIP_EXTRA2, GZIP_ORIG, GZIP_COMMENT, GZIP_CRC };
398 char c;
400 *rs = NS_OK;
402 if (mCheckHeaderDone)
403 return streamLen;
405 while (streamLen)
407 switch (hMode)
409 case GZIP_INIT:
410 iStr->Read (&c, 1, &rv);
411 streamLen--;
413 if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0])
415 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
416 return 0;
419 if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1])
421 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
422 return 0;
425 if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED)
427 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
428 return 0;
431 mSkipCount++;
432 if (mSkipCount == 4)
434 mFlags = (unsigned) c & 0377;
435 if (mFlags & RESERVED)
437 *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
438 return 0;
440 hMode = GZIP_OS;
441 mSkipCount = 0;
443 break;
445 case GZIP_OS:
446 iStr->Read(&c, 1, &rv);
447 streamLen--;
448 mSkipCount++;
450 if (mSkipCount == 6)
451 hMode = GZIP_EXTRA0;
452 break;
454 case GZIP_EXTRA0:
455 if (mFlags & EXTRA_FIELD)
457 iStr->Read(&c, 1, &rv);
458 streamLen--;
459 mLen = (uInt) c & 0377;
460 hMode = GZIP_EXTRA1;
462 else
463 hMode = GZIP_ORIG;
464 break;
466 case GZIP_EXTRA1:
467 iStr->Read(&c, 1, &rv);
468 streamLen--;
469 mLen = ((uInt) c & 0377) << 8;
470 mSkipCount = 0;
471 hMode = GZIP_EXTRA2;
472 break;
474 case GZIP_EXTRA2:
475 if (mSkipCount == mLen)
476 hMode = GZIP_ORIG;
477 else
479 iStr->Read(&c, 1, &rv);
480 streamLen--;
481 mSkipCount++;
483 break;
485 case GZIP_ORIG:
486 if (mFlags & ORIG_NAME)
488 iStr->Read(&c, 1, &rv);
489 streamLen--;
490 if (c == 0)
491 hMode = GZIP_COMMENT;
493 else
494 hMode = GZIP_COMMENT;
495 break;
497 case GZIP_COMMENT:
498 if (mFlags & COMMENT)
500 iStr->Read(&c, 1, &rv);
501 streamLen--;
502 if (c == 0)
504 hMode = GZIP_CRC;
505 mSkipCount = 0;
508 else
510 hMode = GZIP_CRC;
511 mSkipCount = 0;
513 break;
515 case GZIP_CRC:
516 if (mFlags & HEAD_CRC)
518 iStr->Read(&c, 1, &rv);
519 streamLen--;
520 mSkipCount++;
521 if (mSkipCount == 2)
523 mCheckHeaderDone = PR_TRUE;
524 return streamLen;
527 else
529 mCheckHeaderDone = PR_TRUE;
530 return streamLen;
532 break;
535 return streamLen;
538 nsresult
539 NS_NewHTTPCompressConv(nsHTTPCompressConv **aHTTPCompressConv)
541 NS_PRECONDITION(aHTTPCompressConv != nsnull, "null ptr");
543 if (!aHTTPCompressConv)
544 return NS_ERROR_NULL_POINTER;
546 *aHTTPCompressConv = new nsHTTPCompressConv();
548 if (!*aHTTPCompressConv)
549 return NS_ERROR_OUT_OF_MEMORY;
551 NS_ADDREF(*aHTTPCompressConv);
552 return NS_OK;