netwerk/streamconv/converters/nsHTTPCompressConv.cpp

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

mercurial