|
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 } |