Back to index

lightning-sunbird  0.9+nobinonly
nsHTTPCompressConv.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set ts=4 sw=4 sts=4 cindent et: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   David Dick <ddick@cpan.org>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nsHTTPCompressConv.h"
00041 #include "nsMemory.h"
00042 #include "plstr.h"
00043 #include "prlog.h"
00044 #include "nsIChannel.h"
00045 #include "nsCOMPtr.h"
00046 #include "nsReadableUtils.h"
00047 #include "nsIByteArrayInputStream.h"
00048 #include "nsIStringStream.h"
00049 
00050 static NS_METHOD
00051 DiscardSegments(nsIInputStream *input,
00052                 void *closure,
00053                 const char *buf,
00054                 PRUint32 offset,
00055                 PRUint32 count,
00056                 PRUint32 *countRead)
00057 {
00058     *countRead = count;
00059     return NS_OK;
00060 }
00061 
00062 // nsISupports implementation
00063 NS_IMPL_THREADSAFE_ISUPPORTS2(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener)
00064 
00065 // nsFTPDirListingConv methods
00066 nsHTTPCompressConv::nsHTTPCompressConv()
00067     : mListener(nsnull)
00068     , mMode(HTTP_COMPRESS_IDENTITY)
00069     , mOutBuffer(NULL)
00070     , mInpBuffer(NULL)
00071     , mOutBufferLen(0)
00072     , mInpBufferLen(0)
00073     , mCheckHeaderDone(PR_FALSE)
00074     , mStreamEnded(PR_FALSE)
00075     , mStreamInitialized(PR_FALSE)
00076     , mLen(0)
00077     , hMode(0)
00078     , mSkipCount(0)
00079     , mFlags(0)
00080 {
00081 }
00082 
00083 nsHTTPCompressConv::~nsHTTPCompressConv()
00084 {
00085     NS_IF_RELEASE(mListener);
00086 
00087     if (mInpBuffer)
00088         nsMemory::Free(mInpBuffer);
00089 
00090     if (mOutBuffer)
00091         nsMemory::Free(mOutBuffer);
00092 
00093     // For some reason we are not getting Z_STREAM_END.  But this was also seen
00094     //    for mozilla bug 198133.  Need to handle this case.
00095     if ((mStreamInitialized == PR_TRUE) && (mStreamEnded == PR_FALSE))
00096         inflateEnd (&d_stream);
00097 }
00098 
00099 NS_IMETHODIMP
00100 nsHTTPCompressConv::AsyncConvertData(const char *aFromType, 
00101                                      const char *aToType, 
00102                                      nsIStreamListener *aListener, 
00103                                      nsISupports *aCtxt)
00104 {
00105     if (!PL_strncasecmp(aFromType, HTTP_COMPRESS_TYPE, sizeof(HTTP_COMPRESS_TYPE)-1) ||
00106         !PL_strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE, sizeof(HTTP_X_COMPRESS_TYPE)-1))
00107         mMode = HTTP_COMPRESS_COMPRESS;
00108 
00109     else if (!PL_strncasecmp(aFromType, HTTP_GZIP_TYPE, sizeof(HTTP_GZIP_TYPE)-1) ||
00110              !PL_strncasecmp(aFromType, HTTP_X_GZIP_TYPE, sizeof(HTTP_X_GZIP_TYPE)-1))
00111         mMode = HTTP_COMPRESS_GZIP;
00112 
00113     else if (!PL_strncasecmp(aFromType, HTTP_DEFLATE_TYPE, sizeof(HTTP_DEFLATE_TYPE)-1))
00114         mMode = HTTP_COMPRESS_DEFLATE;
00115 
00116     // hook ourself up with the receiving listener. 
00117     mListener = aListener;
00118     NS_ADDREF(mListener);
00119 
00120     mAsyncConvContext = aCtxt;
00121     return NS_OK; 
00122 } 
00123 
00124 NS_IMETHODIMP
00125 nsHTTPCompressConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
00126 {
00127     return mListener->OnStartRequest(request, aContext);
00128 } 
00129 
00130 NS_IMETHODIMP
00131 nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext, 
00132                                   nsresult aStatus)
00133 {
00134     return mListener->OnStopRequest(request, aContext, aStatus);
00135 } 
00136 
00137 NS_IMETHODIMP
00138 nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, 
00139                                     nsISupports *aContext, 
00140                                     nsIInputStream *iStr, 
00141                                     PRUint32 aSourceOffset, 
00142                                     PRUint32 aCount)
00143 {
00144     nsresult rv = NS_ERROR_FAILURE;
00145     PRUint32 streamLen = aCount;
00146 
00147     if (streamLen == 0)
00148     {
00149         NS_ERROR("count of zero passed to OnDataAvailable");
00150         return NS_ERROR_UNEXPECTED;
00151     }
00152 
00153     if (mStreamEnded)
00154     {
00155         // Hmm... this may just indicate that the data stream is done and that
00156         // what's left is either metadata or padding of some sort.... throwing
00157         // it out is probably the safe thing to do.
00158         PRUint32 n;
00159         return iStr->ReadSegments(DiscardSegments, nsnull, streamLen, &n);
00160     }
00161 
00162     switch (mMode)
00163     {
00164         case HTTP_COMPRESS_GZIP:
00165             streamLen = check_header(iStr, streamLen, &rv);
00166 
00167             if (rv != NS_OK)
00168                 return rv;
00169 
00170             if (streamLen == 0)
00171                 return NS_OK;
00172 
00173         case HTTP_COMPRESS_DEFLATE:
00174 
00175             if (mInpBuffer != NULL && streamLen > mInpBufferLen)
00176             {
00177                 mInpBuffer = (unsigned char *) nsMemory::Realloc(mInpBuffer, mInpBufferLen = streamLen);
00178                
00179                 if (mOutBufferLen < streamLen * 2)
00180                     mOutBuffer = (unsigned char *) nsMemory::Realloc(mOutBuffer, mOutBufferLen = streamLen * 3);
00181 
00182                 if (mInpBuffer == NULL || mOutBuffer == NULL)
00183                     return NS_ERROR_OUT_OF_MEMORY;
00184             }
00185 
00186             if (mInpBuffer == NULL)
00187                 mInpBuffer = (unsigned char *) nsMemory::Alloc(mInpBufferLen = streamLen);
00188 
00189             if (mOutBuffer == NULL)
00190                 mOutBuffer = (unsigned char *) nsMemory::Alloc(mOutBufferLen = streamLen * 3);
00191 
00192             if (mInpBuffer == NULL || mOutBuffer == NULL)
00193                 return NS_ERROR_OUT_OF_MEMORY;
00194 
00195             iStr->Read((char *)mInpBuffer, streamLen, &rv);
00196 
00197             if (NS_FAILED(rv))
00198                 return rv;
00199 
00200             if (mMode == HTTP_COMPRESS_DEFLATE)
00201             {
00202                 if (!mStreamInitialized)
00203                 {
00204                     memset(&d_stream, 0, sizeof (d_stream));
00205                 
00206                     if (inflateInit(&d_stream) != Z_OK)
00207                         return NS_ERROR_FAILURE;
00208 
00209                     mStreamInitialized = PR_TRUE;
00210                 }
00211                 d_stream.next_in = mInpBuffer;
00212                 d_stream.avail_in = (uInt)streamLen;
00213 
00214                 mDummyStreamInitialised = PR_FALSE;
00215                 for (;;)
00216                 {
00217                     d_stream.next_out = mOutBuffer;
00218                     d_stream.avail_out = (uInt)mOutBufferLen;
00219  
00220                     int code = inflate(&d_stream, Z_NO_FLUSH);
00221                     unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
00222 
00223                     if (code == Z_STREAM_END)
00224                     {
00225                         if (bytesWritten)
00226                         {
00227                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00228                             if (NS_FAILED (rv))
00229                                 return rv;
00230                         }
00231                         
00232                         inflateEnd(&d_stream);
00233                         mStreamEnded = PR_TRUE;
00234                         break;
00235                     }
00236                     else if (code == Z_OK)
00237                     {
00238                         if (bytesWritten)
00239                         {
00240                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00241                             if (NS_FAILED (rv))
00242                                 return rv;
00243                         }
00244                     }
00245                     else if (code == Z_BUF_ERROR)
00246                     {
00247                         if (bytesWritten)
00248                         {
00249                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00250                             if (NS_FAILED (rv))
00251                                 return rv;
00252                         }
00253                         break;
00254                     }
00255                     else if (code == Z_DATA_ERROR)
00256                     {
00257                         // some servers (notably Apache with mod_deflate) don't generate zlib headers
00258                         // insert a dummy header and try again
00259                         static char dummy_head[2] =
00260                         {
00261                             0x8 + 0x7 * 0x10,
00262                             (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
00263                         };
00264                         inflateReset(&d_stream);
00265                         d_stream.next_in = (Bytef*) dummy_head;
00266                         d_stream.avail_in = sizeof(dummy_head);
00267 
00268                         code = inflate(&d_stream, Z_NO_FLUSH);
00269                         if (code != Z_OK)
00270                             return NS_ERROR_FAILURE;
00271 
00272                         // stop an endless loop caused by non-deflate data being labelled as deflate
00273                         if (mDummyStreamInitialised) {
00274                             NS_ERROR("endless loop detected");
00275                             return NS_ERROR_FAILURE;
00276                         }
00277                         mDummyStreamInitialised = PR_TRUE;
00278                         // reset stream pointers to our original data
00279                         d_stream.next_in = mInpBuffer;
00280                         d_stream.avail_in = (uInt)streamLen;
00281                     }    
00282                     else
00283                         return NS_ERROR_FAILURE;
00284                 } /* for */
00285             }
00286             else
00287             {
00288                 if (!mStreamInitialized)
00289                 {
00290                     memset(&d_stream, 0, sizeof (d_stream));
00291                 
00292                     if (inflateInit2(&d_stream, -MAX_WBITS) != Z_OK)
00293                         return NS_ERROR_FAILURE;
00294 
00295                     mStreamInitialized = PR_TRUE;
00296                 }
00297 
00298                 d_stream.next_in  = mInpBuffer;
00299                 d_stream.avail_in = (uInt)streamLen;
00300 
00301                 for (;;)
00302                 {
00303                     d_stream.next_out  = mOutBuffer;
00304                     d_stream.avail_out = (uInt)mOutBufferLen;
00305  
00306                     int code = inflate (&d_stream, Z_NO_FLUSH);
00307                     unsigned bytesWritten = (uInt)mOutBufferLen - d_stream.avail_out;
00308 
00309                     if (code == Z_STREAM_END)
00310                     {
00311                         if (bytesWritten)
00312                         {
00313                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00314                             if (NS_FAILED (rv))
00315                                 return rv;
00316                         }
00317                         
00318                         inflateEnd(&d_stream);
00319                         mStreamEnded = PR_TRUE;
00320                         break;
00321                     }
00322                     else if (code == Z_OK)
00323                     {
00324                         if (bytesWritten)
00325                         {
00326                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00327                             if (NS_FAILED (rv))
00328                                 return rv;
00329                         }
00330                     }
00331                     else if (code == Z_BUF_ERROR)
00332                     {
00333                         if (bytesWritten)
00334                         {
00335                             rv = do_OnDataAvailable(request, aContext, aSourceOffset, (char *)mOutBuffer, bytesWritten);
00336                             if (NS_FAILED (rv))
00337                                 return rv;
00338                         }
00339                         break;
00340                     }
00341                     else
00342                         return NS_ERROR_FAILURE;
00343                 } /* for */
00344             } /* gzip */
00345             break;
00346 
00347         default: 
00348             rv = mListener->OnDataAvailable(request, aContext, iStr, aSourceOffset, aCount);
00349             if (NS_FAILED (rv))
00350                 return rv;
00351     } /* switch */
00352 
00353        return NS_OK;
00354 } /* OnDataAvailable */
00355 
00356 
00357 // XXX/ruslan: need to implement this too
00358 
00359 NS_IMETHODIMP
00360 nsHTTPCompressConv::Convert(nsIInputStream *aFromStream, 
00361                             const char *aFromType, 
00362                             const char *aToType, 
00363                             nsISupports *aCtxt, 
00364                             nsIInputStream **_retval)
00365 { 
00366     return NS_ERROR_NOT_IMPLEMENTED;
00367 } 
00368 
00369 nsresult
00370 nsHTTPCompressConv::do_OnDataAvailable(nsIRequest* request, nsISupports *aContext, PRUint32 aSourceOffset, char *buffer, PRUint32 aCount)
00371 {
00372     nsresult rv;
00373 
00374     nsCOMPtr<nsIByteArrayInputStream> convertedStreamSup;
00375 
00376     char *lBuf = (char *) nsMemory::Alloc (aCount);
00377     if (lBuf == NULL)
00378         return NS_ERROR_OUT_OF_MEMORY;
00379 
00380     memcpy(lBuf, buffer, aCount);
00381 
00382     rv = NS_NewByteArrayInputStream(getter_AddRefs(convertedStreamSup), lBuf, aCount);
00383     if (NS_FAILED(rv))
00384         return rv;
00385 
00386     nsCOMPtr<nsIInputStream> convertedStream = do_QueryInterface(convertedStreamSup, &rv);
00387     if (NS_FAILED(rv))
00388         return rv;
00389 
00390     return mListener->OnDataAvailable(request, aContext, convertedStream, aSourceOffset, aCount);
00391 }
00392 
00393 #define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
00394 #define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
00395 #define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
00396 #define ORIG_NAME    0x08 /* bit 3 set: original file name present */
00397 #define COMMENT      0x10 /* bit 4 set: file comment present */
00398 #define RESERVED     0xE0 /* bits 5..7: reserved */
00399 
00400 static unsigned gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
00401 
00402 PRUint32
00403 nsHTTPCompressConv::check_header(nsIInputStream *iStr, PRUint32 streamLen, nsresult *rs)
00404 {
00405     nsresult rv;
00406     enum  { GZIP_INIT = 0, GZIP_OS, GZIP_EXTRA0, GZIP_EXTRA1, GZIP_EXTRA2, GZIP_ORIG, GZIP_COMMENT, GZIP_CRC };
00407     char c;
00408 
00409     *rs = NS_OK;
00410 
00411     if (mCheckHeaderDone)
00412         return streamLen;
00413 
00414     while (streamLen)
00415     {
00416         switch (hMode)
00417         {
00418             case GZIP_INIT:
00419                 iStr->Read (&c, 1, &rv);
00420                 streamLen--;
00421                 
00422                 if (mSkipCount == 0 && ((unsigned)c & 0377) != gz_magic[0])
00423                 {
00424                     *rs = NS_ERROR_FAILURE;
00425                     return 0;
00426                 }
00427 
00428                 if (mSkipCount == 1 && ((unsigned)c & 0377) != gz_magic[1])
00429                 {
00430                     *rs = NS_ERROR_FAILURE;
00431                     return 0;
00432                 }
00433 
00434                 if (mSkipCount == 2 && ((unsigned)c & 0377) != Z_DEFLATED)
00435                 {
00436                     *rs = NS_ERROR_FAILURE;
00437                     return 0;
00438                 }
00439 
00440                 mSkipCount++;
00441                 if (mSkipCount == 4)
00442                 {
00443                     mFlags = (unsigned) c & 0377;
00444                     if (mFlags & RESERVED)
00445                     {
00446                         *rs = NS_ERROR_FAILURE;
00447                         return 0;
00448                     }
00449                     hMode = GZIP_OS;
00450                     mSkipCount = 0;
00451                 }
00452                 break;
00453 
00454             case GZIP_OS:
00455                 iStr->Read(&c, 1, &rv);
00456                 streamLen--;
00457                 mSkipCount++;
00458 
00459                 if (mSkipCount == 6)
00460                     hMode = GZIP_EXTRA0;
00461                 break;
00462         
00463             case GZIP_EXTRA0:
00464                 if (mFlags & EXTRA_FIELD)
00465                 {
00466                     iStr->Read(&c, 1, &rv);
00467                     streamLen--;
00468                     mLen = (uInt) c & 0377;
00469                     hMode = GZIP_EXTRA1;
00470                 }
00471                 else
00472                     hMode = GZIP_ORIG;
00473                 break;
00474 
00475             case GZIP_EXTRA1:
00476                 iStr->Read(&c, 1, &rv);
00477                 streamLen--;
00478                 mLen = ((uInt) c & 0377) << 8;
00479                 mSkipCount = 0;
00480                 hMode = GZIP_EXTRA2;
00481                 break;
00482 
00483             case GZIP_EXTRA2:
00484                 if (mSkipCount == mLen)
00485                     hMode = GZIP_ORIG;
00486                 else
00487                 {
00488                     iStr->Read(&c, 1, &rv);
00489                     streamLen--;
00490                     mSkipCount++;
00491                 }
00492                 break;
00493 
00494             case GZIP_ORIG:
00495                 if (mFlags & ORIG_NAME)
00496                 {
00497                     iStr->Read(&c, 1, &rv);
00498                     streamLen--;
00499                     if (c == 0)
00500                         hMode = GZIP_COMMENT;
00501                 }
00502                 else
00503                     hMode = GZIP_COMMENT;
00504                 break;
00505 
00506             case GZIP_COMMENT:
00507                 if (mFlags & GZIP_COMMENT)
00508                 {
00509                     iStr->Read(&c, 1, &rv);
00510                     streamLen--;
00511                     if (c == 0)
00512                     {
00513                         hMode = GZIP_CRC;
00514                         mSkipCount = 0;
00515                     }
00516                 }
00517                 else
00518                 {
00519                     hMode = GZIP_CRC;
00520                     mSkipCount = 0;
00521                 }
00522                 break;
00523 
00524             case GZIP_CRC:
00525                 if (mFlags & HEAD_CRC)
00526                 {
00527                     iStr->Read(&c, 1, &rv);
00528                     streamLen--;
00529                     mSkipCount++;
00530                     if (mSkipCount == 2)
00531                     {
00532                         mCheckHeaderDone = PR_TRUE;
00533                         return streamLen;
00534                     }
00535                 }
00536                 else
00537                 {
00538                     mCheckHeaderDone = PR_TRUE;
00539                     return streamLen;
00540                 }
00541             break;
00542         }
00543     }
00544     return streamLen;
00545 }
00546 
00547 nsresult
00548 NS_NewHTTPCompressConv(nsHTTPCompressConv **aHTTPCompressConv)
00549 {
00550     NS_PRECONDITION(aHTTPCompressConv != nsnull, "null ptr");
00551 
00552     if (!aHTTPCompressConv)
00553         return NS_ERROR_NULL_POINTER;
00554 
00555     *aHTTPCompressConv = new nsHTTPCompressConv();
00556 
00557     if (!*aHTTPCompressConv)
00558         return NS_ERROR_OUT_OF_MEMORY;
00559 
00560     NS_ADDREF(*aHTTPCompressConv);
00561     return NS_OK;
00562 }