michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsUnknownDecoder.h" michael@0: #include "nsIPipe.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsIMIMEService.h" michael@0: michael@0: #include "nsIViewSourceChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: michael@0: #define MAX_BUFFER_SIZE 512 michael@0: michael@0: nsUnknownDecoder::nsUnknownDecoder() michael@0: : mBuffer(nullptr) michael@0: , mBufferLen(0) michael@0: , mRequireHTMLsuffix(false) michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (prefs) { michael@0: bool val; michael@0: if (NS_SUCCEEDED(prefs->GetBoolPref("security.requireHTMLsuffix", &val))) michael@0: mRequireHTMLsuffix = val; michael@0: } michael@0: } michael@0: michael@0: nsUnknownDecoder::~nsUnknownDecoder() michael@0: { michael@0: if (mBuffer) { michael@0: delete [] mBuffer; michael@0: mBuffer = nullptr; michael@0: } michael@0: } michael@0: michael@0: // ---- michael@0: // michael@0: // nsISupports implementation... michael@0: // michael@0: // ---- michael@0: michael@0: NS_IMPL_ADDREF(nsUnknownDecoder) michael@0: NS_IMPL_RELEASE(nsUnknownDecoder) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsUnknownDecoder) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamConverter) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIContentSniffer) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: // ---- michael@0: // michael@0: // nsIStreamConverter methods... michael@0: // michael@0: // ---- michael@0: michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aCtxt, michael@0: nsIInputStream **aResultStream) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::AsyncConvertData(const char *aFromType, michael@0: const char *aToType, michael@0: nsIStreamListener *aListener, michael@0: nsISupports *aCtxt) michael@0: { michael@0: NS_ASSERTION(aListener && aFromType && aToType, michael@0: "null pointer passed into multi mixed converter"); michael@0: // hook up our final listener. this guy gets the various On*() calls we want to throw michael@0: // at him. michael@0: // michael@0: mNextListener = aListener; michael@0: return (aListener) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // ---- michael@0: // michael@0: // nsIStreamListener methods... michael@0: // michael@0: // ---- michael@0: michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::OnDataAvailable(nsIRequest* request, michael@0: nsISupports *aCtxt, michael@0: nsIInputStream *aStream, michael@0: uint64_t aSourceOffset, michael@0: uint32_t aCount) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mNextListener) return NS_ERROR_FAILURE; michael@0: michael@0: if (mContentType.IsEmpty()) { michael@0: uint32_t count, len; michael@0: michael@0: // If the buffer has not been allocated by now, just fail... michael@0: if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // michael@0: // Determine how much of the stream should be read to fill up the michael@0: // sniffer buffer... michael@0: // michael@0: if (mBufferLen + aCount >= MAX_BUFFER_SIZE) { michael@0: count = MAX_BUFFER_SIZE-mBufferLen; michael@0: } else { michael@0: count = aCount; michael@0: } michael@0: michael@0: // Read the data into the buffer... michael@0: rv = aStream->Read((mBuffer+mBufferLen), count, &len); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mBufferLen += len; michael@0: aCount -= len; michael@0: michael@0: if (aCount) { michael@0: // michael@0: // Adjust the source offset... The call to FireListenerNotifications(...) michael@0: // will make the first OnDataAvailable(...) call with an offset of 0. michael@0: // So, this offset needs to be adjusted to reflect that... michael@0: // michael@0: aSourceOffset += mBufferLen; michael@0: michael@0: DetermineContentType(request); michael@0: michael@0: rv = FireListenerNotifications(request, aCtxt); michael@0: } michael@0: } michael@0: michael@0: // Must not fire ODA again if it failed once michael@0: if (aCount && NS_SUCCEEDED(rv)) { michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: michael@0: rv = mNextListener->OnDataAvailable(request, aCtxt, aStream, michael@0: aSourceOffset, aCount); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // ---- michael@0: // michael@0: // nsIRequestObserver methods... michael@0: // michael@0: // ---- michael@0: michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mNextListener) return NS_ERROR_FAILURE; michael@0: michael@0: // Allocate the sniffer buffer... michael@0: if (NS_SUCCEEDED(rv) && !mBuffer) { michael@0: mBuffer = new char[MAX_BUFFER_SIZE]; michael@0: michael@0: if (!mBuffer) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: // Do not pass the OnStartRequest on to the next listener (yet)... michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mNextListener) return NS_ERROR_FAILURE; michael@0: michael@0: // michael@0: // The total amount of data is less than the size of the sniffer buffer. michael@0: // Analyze the buffer now... michael@0: // michael@0: if (mContentType.IsEmpty()) { michael@0: DetermineContentType(request); michael@0: michael@0: rv = FireListenerNotifications(request, aCtxt); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aStatus = rv; michael@0: } michael@0: } michael@0: michael@0: rv = mNextListener->OnStopRequest(request, aCtxt, aStatus); michael@0: mNextListener = 0; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // ---- michael@0: // michael@0: // nsIContentSniffer methods... michael@0: // michael@0: // ---- michael@0: NS_IMETHODIMP michael@0: nsUnknownDecoder::GetMIMETypeFromContent(nsIRequest* aRequest, michael@0: const uint8_t* aData, michael@0: uint32_t aLength, michael@0: nsACString& type) michael@0: { michael@0: mBuffer = const_cast(reinterpret_cast(aData)); michael@0: mBufferLen = aLength; michael@0: DetermineContentType(aRequest); michael@0: mBuffer = nullptr; michael@0: mBufferLen = 0; michael@0: type.Assign(mContentType); michael@0: mContentType.Truncate(); michael@0: return type.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK; michael@0: } michael@0: michael@0: michael@0: // Actual sniffing code michael@0: michael@0: bool nsUnknownDecoder::AllowSniffing(nsIRequest* aRequest) michael@0: { michael@0: if (!mRequireHTMLsuffix) { michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: if (!channel) { michael@0: NS_ERROR("QI failed"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: if (NS_FAILED(channel->GetURI(getter_AddRefs(uri))) || !uri) { michael@0: return false; michael@0: } michael@0: michael@0: bool isLocalFile = false; michael@0: if (NS_FAILED(uri->SchemeIs("file", &isLocalFile)) || isLocalFile) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * This is the array of sniffer entries that depend on "magic numbers" michael@0: * in the file. Each entry has either a type associated with it (set michael@0: * these with the SNIFFER_ENTRY macro) or a function to be executed michael@0: * (set these with the SNIFFER_ENTRY_WITH_FUNC macro). The function michael@0: * should take a single nsIRequest* and returns bool -- true if michael@0: * it sets mContentType, false otherwise michael@0: */ michael@0: nsUnknownDecoder::nsSnifferEntry nsUnknownDecoder::sSnifferEntries[] = { michael@0: SNIFFER_ENTRY("%PDF-", APPLICATION_PDF), michael@0: michael@0: SNIFFER_ENTRY("%!PS-Adobe-", APPLICATION_POSTSCRIPT), michael@0: michael@0: // Files that start with mailbox delimiters let's provisionally call michael@0: // text/plain michael@0: SNIFFER_ENTRY("From", TEXT_PLAIN), michael@0: SNIFFER_ENTRY(">From", TEXT_PLAIN), michael@0: michael@0: // If the buffer begins with "#!" or "%!" then it is a script of michael@0: // some sort... "Scripts" can include arbitrary data to be passed michael@0: // to an interpreter, so we need to decide whether we can call this michael@0: // text or whether it's data. michael@0: SNIFFER_ENTRY_WITH_FUNC("#!", &nsUnknownDecoder::LastDitchSniff), michael@0: michael@0: // XXXbz should (and can) we also include the various ways that = sSnifferEntries[i].mByteLen && // enough data michael@0: memcmp(mBuffer, sSnifferEntries[i].mBytes, sSnifferEntries[i].mByteLen) == 0) { // and type matches michael@0: NS_ASSERTION(sSnifferEntries[i].mMimeType || michael@0: sSnifferEntries[i].mContentTypeSniffer, michael@0: "Must have either a type string or a function to set the type"); michael@0: NS_ASSERTION(!sSnifferEntries[i].mMimeType || michael@0: !sSnifferEntries[i].mContentTypeSniffer, michael@0: "Both a type string and a type sniffing function set;" michael@0: " using type string"); michael@0: if (sSnifferEntries[i].mMimeType) { michael@0: mContentType = sSnifferEntries[i].mMimeType; michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: return; michael@0: } michael@0: if ((this->*(sSnifferEntries[i].mContentTypeSniffer))(aRequest)) { michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest, michael@0: (const uint8_t*)mBuffer, mBufferLen, mContentType); michael@0: if (!mContentType.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: if (SniffForHTML(aRequest)) { michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: return; michael@0: } michael@0: michael@0: // We don't know what this is yet. Before we just give up, try michael@0: // the URI from the request. michael@0: if (SniffURI(aRequest)) { michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: return; michael@0: } michael@0: michael@0: LastDitchSniff(aRequest); michael@0: NS_ASSERTION(!mContentType.IsEmpty(), michael@0: "Content type should be known by now."); michael@0: } michael@0: michael@0: bool nsUnknownDecoder::SniffForHTML(nsIRequest* aRequest) michael@0: { michael@0: /* michael@0: * To prevent a possible attack, we will not consider this to be michael@0: * html content if it comes from the local file system and our prefs michael@0: * are set right michael@0: */ michael@0: if (!AllowSniffing(aRequest)) { michael@0: return false; michael@0: } michael@0: michael@0: // Now look for HTML. michael@0: const char* str = mBuffer; michael@0: const char* end = mBuffer + mBufferLen; michael@0: michael@0: // skip leading whitespace michael@0: while (str != end && nsCRT::IsAsciiSpace(*str)) { michael@0: ++str; michael@0: } michael@0: michael@0: // did we find something like a start tag? michael@0: if (str == end || *str != '<' || ++str == end) { michael@0: return false; michael@0: } michael@0: michael@0: // If we seem to be SGML or XML and we got down here, just pretend we're HTML michael@0: if (*str == '!' || *str == '?') { michael@0: mContentType = TEXT_HTML; michael@0: return true; michael@0: } michael@0: michael@0: uint32_t bufSize = end - str; michael@0: // We use sizeof(_tagstr) below because that's the length of _tagstr michael@0: // with the one char " " or ">" appended. michael@0: #define MATCHES_TAG(_tagstr) \ michael@0: (bufSize >= sizeof(_tagstr) && \ michael@0: (PL_strncasecmp(str, _tagstr " ", sizeof(_tagstr)) == 0 || \ michael@0: PL_strncasecmp(str, _tagstr ">", sizeof(_tagstr)) == 0)) michael@0: michael@0: if (MATCHES_TAG("html") || michael@0: MATCHES_TAG("frameset") || michael@0: MATCHES_TAG("body") || michael@0: MATCHES_TAG("head") || michael@0: MATCHES_TAG("script") || michael@0: MATCHES_TAG("iframe") || michael@0: MATCHES_TAG("a") || michael@0: MATCHES_TAG("img") || michael@0: MATCHES_TAG("table") || michael@0: MATCHES_TAG("title") || michael@0: MATCHES_TAG("link") || michael@0: MATCHES_TAG("base") || michael@0: MATCHES_TAG("style") || michael@0: MATCHES_TAG("div") || michael@0: MATCHES_TAG("p") || michael@0: MATCHES_TAG("font") || michael@0: MATCHES_TAG("applet") || michael@0: MATCHES_TAG("meta") || michael@0: MATCHES_TAG("center") || michael@0: MATCHES_TAG("form") || michael@0: MATCHES_TAG("isindex") || michael@0: MATCHES_TAG("h1") || michael@0: MATCHES_TAG("h2") || michael@0: MATCHES_TAG("h3") || michael@0: MATCHES_TAG("h4") || michael@0: MATCHES_TAG("h5") || michael@0: MATCHES_TAG("h6") || michael@0: MATCHES_TAG("b") || michael@0: MATCHES_TAG("pre")) { michael@0: michael@0: mContentType = TEXT_HTML; michael@0: return true; michael@0: } michael@0: michael@0: #undef MATCHES_TAG michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool nsUnknownDecoder::SniffForXML(nsIRequest* aRequest) michael@0: { michael@0: // Just like HTML, this should be able to be shut off. michael@0: if (!AllowSniffing(aRequest)) { michael@0: return false; michael@0: } michael@0: michael@0: // First see whether we can glean anything from the uri... michael@0: if (!SniffURI(aRequest)) { michael@0: // Oh well; just generic XML will have to do michael@0: mContentType = TEXT_XML; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool nsUnknownDecoder::SniffURI(nsIRequest* aRequest) michael@0: { michael@0: nsCOMPtr mimeService(do_GetService("@mozilla.org/mime;1")); michael@0: if (mimeService) { michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: if (channel) { michael@0: nsCOMPtr uri; michael@0: nsresult result = channel->GetURI(getter_AddRefs(uri)); michael@0: if (NS_SUCCEEDED(result) && uri) { michael@0: nsAutoCString type; michael@0: result = mimeService->GetTypeFromURI(uri, type); michael@0: if (NS_SUCCEEDED(result)) { michael@0: mContentType = type; michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // This macro is based on RFC 2046 Section 4.1.2. Treat any char 0-31 michael@0: // except the 9-13 range (\t, \n, \v, \f, \r) and char 27 (used by michael@0: // encodings like Shift_JIS) as non-text michael@0: #define IS_TEXT_CHAR(ch) \ michael@0: (((unsigned char)(ch)) > 31 || (9 <= (ch) && (ch) <= 13) || (ch) == 27) michael@0: michael@0: bool nsUnknownDecoder::LastDitchSniff(nsIRequest* aRequest) michael@0: { michael@0: // All we can do now is try to guess whether this is text/plain or michael@0: // application/octet-stream michael@0: michael@0: // First, check for a BOM. If we see one, assume this is text/plain michael@0: // in whatever encoding. If there is a BOM _and_ text we will michael@0: // always have at least 4 bytes in the buffer (since the 2-byte BOMs michael@0: // are for 2-byte encodings and the UTF-8 BOM is 3 bytes). michael@0: if (mBufferLen >= 4) { michael@0: const unsigned char* buf = (const unsigned char*)mBuffer; michael@0: if ((buf[0] == 0xFE && buf[1] == 0xFF) || // UTF-16, Big Endian michael@0: (buf[0] == 0xFF && buf[1] == 0xFE) || // UTF-16 or UCS-4, Little Endian michael@0: (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) || // UTF-8 michael@0: (buf[0] == 0 && buf[1] == 0 && buf[2] == 0xFE && buf[3] == 0xFF)) { // UCS-4, Big Endian michael@0: michael@0: mContentType = TEXT_PLAIN; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Now see whether the buffer has any non-text chars. If not, then let's michael@0: // just call it text/plain... michael@0: // michael@0: uint32_t i; michael@0: for (i = 0; i < mBufferLen && IS_TEXT_CHAR(mBuffer[i]); i++) { michael@0: continue; michael@0: } michael@0: michael@0: if (i == mBufferLen) { michael@0: mContentType = TEXT_PLAIN; michael@0: } michael@0: else { michael@0: mContentType = APPLICATION_OCTET_STREAM; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request, michael@0: nsISupports *aCtxt) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mNextListener) return NS_ERROR_FAILURE; michael@0: michael@0: if (!mContentType.IsEmpty()) { michael@0: nsCOMPtr viewSourceChannel = michael@0: do_QueryInterface(request); michael@0: if (viewSourceChannel) { michael@0: rv = viewSourceChannel->SetOriginalContentType(mContentType); michael@0: } else { michael@0: nsCOMPtr channel = do_QueryInterface(request, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Set the new content type on the channel... michael@0: rv = channel->SetContentType(mContentType); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to set content type on channel!"); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // Cancel the request to make sure it has the correct status if michael@0: // mNextListener looks at it. michael@0: request->Cancel(rv); michael@0: mNextListener->OnStartRequest(request, aCtxt); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // Fire the OnStartRequest(...) michael@0: rv = mNextListener->OnStartRequest(request, aCtxt); michael@0: michael@0: if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // If the request was canceled, then we need to treat that equivalently michael@0: // to an error returned by OnStartRequest. michael@0: if (NS_SUCCEEDED(rv)) michael@0: request->GetStatus(&rv); michael@0: michael@0: // Fire the first OnDataAvailable for the data that was read from the michael@0: // stream into the sniffer buffer... michael@0: if (NS_SUCCEEDED(rv) && (mBufferLen > 0)) { michael@0: uint32_t len = 0; michael@0: nsCOMPtr in; michael@0: nsCOMPtr out; michael@0: michael@0: // Create a pipe and fill it with the data from the sniffer buffer. michael@0: rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), michael@0: MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = out->Write(mBuffer, mBufferLen, &len); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (len == mBufferLen) { michael@0: rv = mNextListener->OnDataAvailable(request, aCtxt, in, 0, len); michael@0: } else { michael@0: NS_ERROR("Unable to write all the data into the pipe."); michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: delete [] mBuffer; michael@0: mBuffer = nullptr; michael@0: mBufferLen = 0; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsBinaryDetector::DetermineContentType(nsIRequest* aRequest) michael@0: { michael@0: nsCOMPtr httpChannel = do_QueryInterface(aRequest); michael@0: if (!httpChannel) { michael@0: return; michael@0: } michael@0: michael@0: // It's an HTTP channel. Check for the text/plain mess michael@0: nsAutoCString contentTypeHdr; michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"), michael@0: contentTypeHdr); michael@0: nsAutoCString contentType; michael@0: httpChannel->GetContentType(contentType); michael@0: michael@0: // Make sure to do a case-sensitive exact match comparison here. Apache michael@0: // 1.x just sends text/plain for "unknown", while Apache 2.x sends michael@0: // text/plain with a ISO-8859-1 charset. Debian's Apache version, just to michael@0: // be different, sends text/plain with iso-8859-1 charset. For extra fun, michael@0: // FC7, RHEL4, and Ubuntu Feisty send charset=UTF-8. Don't do general michael@0: // case-insensitive comparison, since we really want to apply this crap as michael@0: // rarely as we can. michael@0: if (!contentType.EqualsLiteral("text/plain") || michael@0: (!contentTypeHdr.EqualsLiteral("text/plain") && michael@0: !contentTypeHdr.EqualsLiteral("text/plain; charset=ISO-8859-1") && michael@0: !contentTypeHdr.EqualsLiteral("text/plain; charset=iso-8859-1") && michael@0: !contentTypeHdr.EqualsLiteral("text/plain; charset=UTF-8"))) { michael@0: return; michael@0: } michael@0: michael@0: // Check whether we have content-encoding. If we do, don't try to michael@0: // detect the type. michael@0: // XXXbz we could improve this by doing a local decompress if we michael@0: // wanted, I'm sure. michael@0: nsAutoCString contentEncoding; michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), michael@0: contentEncoding); michael@0: if (!contentEncoding.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: LastDitchSniff(aRequest); michael@0: if (mContentType.Equals(APPLICATION_OCTET_STREAM)) { michael@0: // We want to guess at it instead michael@0: mContentType = APPLICATION_GUESS_FROM_EXT; michael@0: } else { michael@0: // Let the text/plain type we already have be, so that other content michael@0: // sniffers can also get a shot at this data. michael@0: mContentType.Truncate(); michael@0: } michael@0: }