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
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
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.
22 : *
23 : * Contributor(s):
24 : * David Dick <ddick@cpan.org>
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
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"
51 :
52 : // nsISupports implementation
53 121 : NS_IMPL_THREADSAFE_ISUPPORTS3(nsHTTPCompressConv,
54 : nsIStreamConverter,
55 : nsIStreamListener,
56 : nsIRequestObserver)
57 :
58 : // nsFTPDirListingConv methods
59 6 : nsHTTPCompressConv::nsHTTPCompressConv()
60 : : mListener(nsnull)
61 : , mMode(HTTP_COMPRESS_IDENTITY)
62 : , mOutBuffer(NULL)
63 : , mInpBuffer(NULL)
64 : , mOutBufferLen(0)
65 : , mInpBufferLen(0)
66 : , mCheckHeaderDone(false)
67 : , mStreamEnded(false)
68 : , mStreamInitialized(false)
69 : , mLen(0)
70 : , hMode(0)
71 : , mSkipCount(0)
72 6 : , mFlags(0)
73 : {
74 6 : }
75 :
76 18 : nsHTTPCompressConv::~nsHTTPCompressConv()
77 : {
78 6 : NS_IF_RELEASE(mListener);
79 :
80 6 : if (mInpBuffer)
81 5 : nsMemory::Free(mInpBuffer);
82 :
83 6 : if (mOutBuffer)
84 5 : nsMemory::Free(mOutBuffer);
85 :
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 6 : if (mStreamInitialized && !mStreamEnded)
89 0 : inflateEnd (&d_stream);
90 24 : }
91 :
92 : NS_IMETHODIMP
93 6 : nsHTTPCompressConv::AsyncConvertData(const char *aFromType,
94 : const char *aToType,
95 : nsIStreamListener *aListener,
96 : nsISupports *aCtxt)
97 : {
98 12 : if (!PL_strncasecmp(aFromType, HTTP_COMPRESS_TYPE, sizeof(HTTP_COMPRESS_TYPE)-1) ||
99 6 : !PL_strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE, sizeof(HTTP_X_COMPRESS_TYPE)-1))
100 0 : mMode = HTTP_COMPRESS_COMPRESS;
101 :
102 6 : else if (!PL_strncasecmp(aFromType, HTTP_GZIP_TYPE, sizeof(HTTP_GZIP_TYPE)-1) ||
103 0 : !PL_strncasecmp(aFromType, HTTP_X_GZIP_TYPE, sizeof(HTTP_X_GZIP_TYPE)-1))
104 6 : mMode = HTTP_COMPRESS_GZIP;
105 :
106 0 : else if (!PL_strncasecmp(aFromType, HTTP_DEFLATE_TYPE, sizeof(HTTP_DEFLATE_TYPE)-1))
107 0 : mMode = HTTP_COMPRESS_DEFLATE;
108 :
109 : // hook ourself up with the receiving listener.
110 6 : mListener = aListener;
111 6 : NS_ADDREF(mListener);
112 :
113 6 : mAsyncConvContext = aCtxt;
114 6 : return NS_OK;
115 : }
116 :
117 : NS_IMETHODIMP
118 2 : nsHTTPCompressConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
119 : {
120 2 : return mListener->OnStartRequest(request, aContext);
121 : }
122 :
123 : NS_IMETHODIMP
124 6 : nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
125 : nsresult aStatus)
126 : {
127 6 : return mListener->OnStopRequest(request, aContext, aStatus);
128 : }
129 :
130 : NS_IMETHODIMP
131 6 : nsHTTPCompressConv::OnDataAvailable(nsIRequest* request,
132 : nsISupports *aContext,
133 : nsIInputStream *iStr,
134 : PRUint32 aSourceOffset,
135 : PRUint32 aCount)
136 : {
137 6 : nsresult rv = NS_ERROR_INVALID_CONTENT_ENCODING;
138 6 : PRUint32 streamLen = aCount;
139 :
140 6 : if (streamLen == 0)
141 : {
142 0 : NS_ERROR("count of zero passed to OnDataAvailable");
143 0 : return NS_ERROR_UNEXPECTED;
144 : }
145 :
146 6 : if (mStreamEnded)
147 : {
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 0 : return iStr->ReadSegments(NS_DiscardSegment, nsnull, streamLen, &n);
153 : }
154 :
155 6 : switch (mMode)
156 : {
157 : case HTTP_COMPRESS_GZIP:
158 6 : streamLen = check_header(iStr, streamLen, &rv);
159 :
160 6 : if (rv != NS_OK)
161 0 : return rv;
162 :
163 6 : if (streamLen == 0)
164 1 : return NS_OK;
165 :
166 : // FALLTHROUGH
167 :
168 : case HTTP_COMPRESS_DEFLATE:
169 :
170 5 : if (mInpBuffer != NULL && streamLen > mInpBufferLen)
171 : {
172 0 : mInpBuffer = (unsigned char *) nsMemory::Realloc(mInpBuffer, mInpBufferLen = streamLen);
173 :
174 0 : if (mOutBufferLen < streamLen * 2)
175 0 : mOutBuffer = (unsigned char *) nsMemory::Realloc(mOutBuffer, mOutBufferLen = streamLen * 3);
176 :
177 0 : if (mInpBuffer == NULL || mOutBuffer == NULL)
178 0 : return NS_ERROR_OUT_OF_MEMORY;
179 : }
180 :
181 5 : if (mInpBuffer == NULL)
182 5 : mInpBuffer = (unsigned char *) nsMemory::Alloc(mInpBufferLen = streamLen);
183 :
184 5 : if (mOutBuffer == NULL)
185 5 : mOutBuffer = (unsigned char *) nsMemory::Alloc(mOutBufferLen = streamLen * 3);
186 :
187 5 : if (mInpBuffer == NULL || mOutBuffer == NULL)
188 0 : return NS_ERROR_OUT_OF_MEMORY;
189 :
190 5 : iStr->Read((char *)mInpBuffer, streamLen, &rv);
191 :
192 5 : if (NS_FAILED(rv))
193 0 : return rv;
194 :
195 5 : if (mMode == HTTP_COMPRESS_DEFLATE)
196 : {
197 0 : if (!mStreamInitialized)
198 : {
199 0 : memset(&d_stream, 0, sizeof (d_stream));
200 :
201 0 : if (inflateInit(&d_stream) != Z_OK)
202 0 : return NS_ERROR_FAILURE;
203 :
204 0 : mStreamInitialized = true;
205 : }
206 0 : d_stream.next_in = mInpBuffer;
207 0 : d_stream.avail_in = (uInt)streamLen;
208 :
209 0 : mDummyStreamInitialised = false;
210 0 : for (;;)
211 : {
212 0 : d_stream.next_out = mOutBuffer;
213 0 : d_stream.avail_out = (uInt)mOutBufferLen;
214 :
215 0 : int code = inflate(&d_stream, Z_NO_FLUSH);
216 0 : unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
217 :
218 0 : if (code == Z_STREAM_END)
219 : {
220 0 : if (bytesWritten)
221 : {
222 0 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
223 0 : if (NS_FAILED (rv))
224 0 : return rv;
225 : }
226 :
227 0 : inflateEnd(&d_stream);
228 0 : mStreamEnded = true;
229 0 : break;
230 : }
231 0 : else if (code == Z_OK)
232 : {
233 0 : if (bytesWritten)
234 : {
235 0 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
236 0 : if (NS_FAILED (rv))
237 0 : return rv;
238 : }
239 : }
240 0 : else if (code == Z_BUF_ERROR)
241 : {
242 0 : if (bytesWritten)
243 : {
244 0 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
245 0 : if (NS_FAILED (rv))
246 0 : return rv;
247 : }
248 0 : break;
249 : }
250 0 : else if (code == Z_DATA_ERROR)
251 : {
252 : // some servers (notably Apache with mod_deflate) don't generate zlib headers
253 : // insert a dummy header and try again
254 : static char dummy_head[2] =
255 : {
256 : 0x8 + 0x7 * 0x10,
257 : (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
258 : };
259 0 : inflateReset(&d_stream);
260 0 : d_stream.next_in = (Bytef*) dummy_head;
261 0 : d_stream.avail_in = sizeof(dummy_head);
262 :
263 0 : code = inflate(&d_stream, Z_NO_FLUSH);
264 0 : if (code != Z_OK)
265 0 : return NS_ERROR_FAILURE;
266 :
267 : // stop an endless loop caused by non-deflate data being labelled as deflate
268 0 : if (mDummyStreamInitialised) {
269 : NS_WARNING("endless loop detected"
270 0 : " - invalid deflate");
271 0 : return NS_ERROR_INVALID_CONTENT_ENCODING;
272 : }
273 0 : mDummyStreamInitialised = true;
274 : // reset stream pointers to our original data
275 0 : d_stream.next_in = mInpBuffer;
276 0 : d_stream.avail_in = (uInt)streamLen;
277 : }
278 : else
279 0 : return NS_ERROR_INVALID_CONTENT_ENCODING;
280 : } /* for */
281 : }
282 : else
283 : {
284 5 : if (!mStreamInitialized)
285 : {
286 5 : memset(&d_stream, 0, sizeof (d_stream));
287 :
288 5 : if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK)
289 0 : return NS_ERROR_FAILURE;
290 :
291 5 : mStreamInitialized = true;
292 : }
293 :
294 5 : d_stream.next_in = mInpBuffer;
295 5 : d_stream.avail_in = (uInt)streamLen;
296 :
297 2 : for (;;)
298 : {
299 7 : d_stream.next_out = mOutBuffer;
300 7 : d_stream.avail_out = (uInt)mOutBufferLen;
301 :
302 7 : int code = inflate (&d_stream, Z_NO_FLUSH);
303 7 : unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
304 :
305 7 : if (code == Z_STREAM_END)
306 : {
307 5 : if (bytesWritten)
308 : {
309 5 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
310 5 : if (NS_FAILED (rv))
311 0 : return rv;
312 : }
313 :
314 5 : inflateEnd(&d_stream);
315 5 : mStreamEnded = true;
316 5 : break;
317 : }
318 2 : else if (code == Z_OK)
319 : {
320 2 : if (bytesWritten)
321 : {
322 2 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
323 2 : if (NS_FAILED (rv))
324 0 : return rv;
325 : }
326 : }
327 0 : else if (code == Z_BUF_ERROR)
328 : {
329 0 : if (bytesWritten)
330 : {
331 0 : rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
332 0 : if (NS_FAILED (rv))
333 0 : return rv;
334 : }
335 0 : break;
336 : }
337 : else
338 0 : return NS_ERROR_INVALID_CONTENT_ENCODING;
339 : } /* for */
340 : } /* gzip */
341 5 : break;
342 :
343 : default:
344 0 : rv = mListener->OnDataAvailable(request, aContext, iStr, aSourceOffset, aCount);
345 0 : if (NS_FAILED (rv))
346 0 : return rv;
347 : } /* switch */
348 :
349 5 : return NS_OK;
350 : } /* OnDataAvailable */
351 :
352 :
353 : // XXX/ruslan: need to implement this too
354 :
355 : NS_IMETHODIMP
356 0 : nsHTTPCompressConv::Convert(nsIInputStream *aFromStream,
357 : const char *aFromType,
358 : const char *aToType,
359 : nsISupports *aCtxt,
360 : nsIInputStream **_retval)
361 : {
362 0 : return NS_ERROR_NOT_IMPLEMENTED;
363 : }
364 :
365 : nsresult
366 7 : nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request,
367 : nsISupports *context, PRUint32 offset,
368 : const char *buffer, PRUint32 count)
369 : {
370 7 : if (!mStream) {
371 5 : mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
372 5 : NS_ENSURE_STATE(mStream);
373 : }
374 :
375 7 : mStream->ShareData(buffer, count);
376 :
377 : nsresult rv = mListener->OnDataAvailable(request, context, mStream,
378 7 : offset, count);
379 :
380 : // Make sure the stream no longer references |buffer| in case our listener
381 : // is crazy enough to try to read from |mStream| after ODA.
382 7 : mStream->ShareData("", 0);
383 :
384 7 : return rv;
385 : }
386 :
387 : #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
388 : #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
389 : #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
390 : #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
391 : #define COMMENT 0x10 /* bit 4 set: file comment present */
392 : #define RESERVED 0xE0 /* bits 5..7: reserved */
393 :
394 : static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
395 :
396 : PRUint32
397 6 : nsHTTPCompressConv::check_header(nsIInputStream *iStr, PRUint32 streamLen, nsresult *rs)
398 : {
399 : nsresult rv;
400 : enum { GZIP_INIT = 0, GZIP_OS, GZIP_EXTRA0, GZIP_EXTRA1, GZIP_EXTRA2, GZIP_ORIG, GZIP_COMMENT, GZIP_CRC };
401 : char c;
402 :
403 6 : *rs = NS_OK;
404 :
405 6 : if (mCheckHeaderDone)
406 0 : return streamLen;
407 :
408 87 : while (streamLen)
409 : {
410 80 : switch (hMode)
411 : {
412 : case GZIP_INIT:
413 24 : iStr->Read (&c, 1, &rv);
414 24 : streamLen--;
415 :
416 24 : if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0])
417 : {
418 0 : *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
419 0 : return 0;
420 : }
421 :
422 24 : if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1])
423 : {
424 0 : *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
425 0 : return 0;
426 : }
427 :
428 24 : if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED)
429 : {
430 0 : *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
431 0 : return 0;
432 : }
433 :
434 24 : mSkipCount++;
435 24 : if (mSkipCount == 4)
436 : {
437 6 : mFlags = (unsigned) c & 0377;
438 6 : if (mFlags & RESERVED)
439 : {
440 0 : *rs = NS_ERROR_INVALID_CONTENT_ENCODING;
441 0 : return 0;
442 : }
443 6 : hMode = GZIP_OS;
444 6 : mSkipCount = 0;
445 : }
446 24 : break;
447 :
448 : case GZIP_OS:
449 30 : iStr->Read(&c, 1, &rv);
450 30 : streamLen--;
451 30 : mSkipCount++;
452 :
453 30 : if (mSkipCount == 6)
454 5 : hMode = GZIP_EXTRA0;
455 30 : break;
456 :
457 : case GZIP_EXTRA0:
458 5 : if (mFlags & EXTRA_FIELD)
459 : {
460 0 : iStr->Read(&c, 1, &rv);
461 0 : streamLen--;
462 0 : mLen = (uInt) c & 0377;
463 0 : hMode = GZIP_EXTRA1;
464 : }
465 : else
466 5 : hMode = GZIP_ORIG;
467 5 : break;
468 :
469 : case GZIP_EXTRA1:
470 0 : iStr->Read(&c, 1, &rv);
471 0 : streamLen--;
472 0 : mLen = ((uInt) c & 0377) << 8;
473 0 : mSkipCount = 0;
474 0 : hMode = GZIP_EXTRA2;
475 0 : break;
476 :
477 : case GZIP_EXTRA2:
478 0 : if (mSkipCount == mLen)
479 0 : hMode = GZIP_ORIG;
480 : else
481 : {
482 0 : iStr->Read(&c, 1, &rv);
483 0 : streamLen--;
484 0 : mSkipCount++;
485 : }
486 0 : break;
487 :
488 : case GZIP_ORIG:
489 11 : if (mFlags & ORIG_NAME)
490 : {
491 8 : iStr->Read(&c, 1, &rv);
492 8 : streamLen--;
493 8 : if (c == 0)
494 2 : hMode = GZIP_COMMENT;
495 : }
496 : else
497 3 : hMode = GZIP_COMMENT;
498 11 : break;
499 :
500 : case GZIP_COMMENT:
501 5 : if (mFlags & COMMENT)
502 : {
503 0 : iStr->Read(&c, 1, &rv);
504 0 : streamLen--;
505 0 : if (c == 0)
506 : {
507 0 : hMode = GZIP_CRC;
508 0 : mSkipCount = 0;
509 : }
510 : }
511 : else
512 : {
513 5 : hMode = GZIP_CRC;
514 5 : mSkipCount = 0;
515 : }
516 5 : break;
517 :
518 : case GZIP_CRC:
519 5 : if (mFlags & HEAD_CRC)
520 : {
521 0 : iStr->Read(&c, 1, &rv);
522 0 : streamLen--;
523 0 : mSkipCount++;
524 0 : if (mSkipCount == 2)
525 : {
526 0 : mCheckHeaderDone = true;
527 0 : return streamLen;
528 : }
529 : }
530 : else
531 : {
532 5 : mCheckHeaderDone = true;
533 5 : return streamLen;
534 : }
535 0 : break;
536 : }
537 : }
538 1 : return streamLen;
539 : }
540 :
541 : nsresult
542 6 : NS_NewHTTPCompressConv(nsHTTPCompressConv **aHTTPCompressConv)
543 : {
544 6 : NS_PRECONDITION(aHTTPCompressConv != nsnull, "null ptr");
545 :
546 6 : if (!aHTTPCompressConv)
547 0 : return NS_ERROR_NULL_POINTER;
548 :
549 6 : *aHTTPCompressConv = new nsHTTPCompressConv();
550 :
551 6 : if (!*aHTTPCompressConv)
552 0 : return NS_ERROR_OUT_OF_MEMORY;
553 :
554 6 : NS_ADDREF(*aHTTPCompressConv);
555 6 : return NS_OK;
556 : }
|