Bug 1043256 - If unKnownDecoder needs to be used, Content_Encodings will be loaded before OnStartRequest is called on the listener. This makes an error in e10s because Content_Encodings will be loaded before HttpChannelParent can disable them. r=bagder

This commit is contained in:
Dragana Damjanovic 2014-08-13 10:52:00 -04:00
parent 8b1073fa22
commit 29ae85f646
8 changed files with 283 additions and 45 deletions

View File

@ -6,6 +6,9 @@
#include "nsISupports.idl"
interface nsIUTF8StringEnumerator;
interface nsIStreamListener;
interface nsISupports;
/**
* A channel interface which allows special handling of encoded content
*/
@ -41,4 +44,12 @@ interface nsIEncodedChannel : nsISupports
* TRUE by default.
*/
attribute boolean applyConversion;
/**
* This function will start converters if they are available.
* aNewNextListener will be nullptr if no converter is available.
*/
void doApplyContentConversions(in nsIStreamListener aNextListener,
out nsIStreamListener aNewNextListener,
in nsISupports aCtxt);
};

View File

@ -604,13 +604,17 @@ HttpBaseChannel::SetApplyConversion(bool value)
return NS_OK;
}
nsresult
HttpBaseChannel::ApplyContentConversions()
NS_IMETHODIMP
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
nsIStreamListener** aNewNextListener,
nsISupports *aCtxt)
{
*aNewNextListener = nullptr;
nsCOMPtr<nsIStreamListener> nextListener = aNextListener;
if (!mResponseHead)
return NS_OK;
LOG(("HttpBaseChannel::ApplyContentConversions [this=%p]\n", this));
LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
if (!mApplyConversion) {
LOG(("not applying conversion per mApplyConversion\n"));
@ -659,8 +663,8 @@ HttpBaseChannel::ApplyContentConversions()
ToLowerCase(from);
rv = serv->AsyncConvertData(from.get(),
"uncompressed",
mListener,
mListenerContext,
nextListener,
aCtxt,
getter_AddRefs(converter));
if (NS_FAILED(rv)) {
LOG(("Unexpected failure of AsyncConvertData %s\n", val));
@ -668,14 +672,15 @@ HttpBaseChannel::ApplyContentConversions()
}
LOG(("converter removed '%s' content-encoding\n", val));
mListener = converter;
nextListener = converter;
}
else {
if (val)
LOG(("Unknown content encoding '%s', ignoring\n", val));
}
}
*aNewNextListener = nextListener;
NS_ADDREF(*aNewNextListener);
return NS_OK;
}

View File

@ -240,7 +240,9 @@ protected:
// drop reference to listener, its callbacks, and the progress sink
void ReleaseListeners();
nsresult ApplyContentConversions();
NS_IMETHOD DoApplyContentConversions(nsIStreamListener *aNextListener,
nsIStreamListener **aNewNextListener,
nsISupports *aCtxt);
void AddCookiesToRequest();
virtual nsresult SetupReplacementChannel(nsIURI *,

View File

@ -336,9 +336,14 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
if (mResponseHead)
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
rv = ApplyContentConversions();
if (NS_FAILED(rv))
nsCOMPtr<nsIStreamListener> listener;
rv = DoApplyContentConversions(mListener, getter_AddRefs(listener),
mListenerContext);
if (NS_FAILED(rv)) {
Cancel(rv);
} else if (listener) {
mListener = listener;
}
mSelfAddr = selfAddr;
mPeerAddr = peerAddr;

View File

@ -868,6 +868,7 @@ nsHttpChannel::CallOnStartRequest()
((mResponseHead->ContentType().EqualsLiteral(APPLICATION_OCTET_STREAM) &&
(mLoadFlags & LOAD_TREAT_APPLICATION_OCTET_STREAM_AS_UNKNOWN))));
bool unknownDecoderStarted = false;
if (shouldSniff) {
MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
if (!mContentTypeHint.IsEmpty())
@ -877,10 +878,6 @@ nsHttpChannel::CallOnStartRequest()
mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
else {
// Uh-oh. We had better find out what type we are!
// XXX This does not work with content-encodings... but
// neither does applying the conversion from the URILoader
nsCOMPtr<nsIStreamConverterService> serv;
rv = gHttpHandler->
GetStreamConverterService(getter_AddRefs(serv));
@ -894,6 +891,7 @@ nsHttpChannel::CallOnStartRequest()
getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
mListener = converter;
unknownDecoderStarted = true;
}
}
}
@ -924,9 +922,20 @@ nsHttpChannel::CallOnStartRequest()
NS_WARNING("OnStartRequest skipped because of null listener");
}
// install stream converter if required
rv = ApplyContentConversions();
if (NS_FAILED(rv)) return rv;
// Install stream converter if required.
// If we use unknownDecoder, stream converters will be installed later (in
// nsUnknownDecoder) after OnStartRequest is called for the real listener.
if (!unknownDecoderStarted) {
nsCOMPtr<nsIStreamListener> listener;
nsISupports *ctxt = mListenerContext;
rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
if (NS_FAILED(rv)) {
return rv;
}
if (listener) {
mListener = listener;
}
}
rv = EnsureAssocReq();
if (NS_FAILED(rv))

View File

@ -18,16 +18,76 @@
#include "nsIViewSourceChannel.h"
#include "nsIHttpChannel.h"
#include "nsIForcePendingChannel.h"
#include "nsIEncodedChannel.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include <algorithm>
#define MAX_BUFFER_SIZE 512
#define MAX_BUFFER_SIZE 512u
NS_IMPL_ISUPPORTS(nsUnknownDecoder::ConvertedStreamListener,
nsIStreamListener,
nsIRequestObserver)
nsUnknownDecoder::ConvertedStreamListener::
ConvertedStreamListener(nsUnknownDecoder *aDecoder)
{
mDecoder = aDecoder;
}
nsUnknownDecoder::ConvertedStreamListener::~ConvertedStreamListener()
{
}
NS_IMETHODIMP
nsUnknownDecoder::ConvertedStreamListener::
AppendDataToString(nsIInputStream* inputStream,
void* closure,
const char* rawSegment,
uint32_t toOffset,
uint32_t count,
uint32_t* writeCount)
{
nsCString* decodedData = static_cast<nsCString*>(closure);
decodedData->Append(rawSegment, count);
*writeCount = count;
return NS_OK;
}
NS_IMETHODIMP
nsUnknownDecoder::ConvertedStreamListener::OnStartRequest(nsIRequest* request,
nsISupports* context)
{
return NS_OK;
}
NS_IMETHODIMP
nsUnknownDecoder::ConvertedStreamListener::
OnDataAvailable(nsIRequest* request,
nsISupports* context,
nsIInputStream* stream,
uint64_t offset,
uint32_t count)
{
uint32_t read;
return stream->ReadSegments(AppendDataToString, &mDecoder->mDecodedData, count,
&read);
}
NS_IMETHODIMP
nsUnknownDecoder::ConvertedStreamListener::OnStopRequest(nsIRequest* request,
nsISupports* context,
nsresult status)
{
return NS_OK;
}
nsUnknownDecoder::nsUnknownDecoder()
: mBuffer(nullptr)
, mBufferLen(0)
, mRequireHTMLsuffix(false)
, mDecodedData("")
{
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
@ -314,12 +374,26 @@ void nsUnknownDecoder::DetermineContentType(nsIRequest* aRequest)
NS_ASSERTION(mContentType.IsEmpty(), "Content type is already known.");
if (!mContentType.IsEmpty()) return;
const char* testData = mBuffer;
uint32_t testDataLen = mBufferLen;
// Check if data are compressed.
nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(aRequest));
if (channel) {
nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
if (NS_SUCCEEDED(rv)) {
if (!mDecodedData.IsEmpty()) {
testData = mDecodedData.get();
testDataLen = std::min(mDecodedData.Length(), MAX_BUFFER_SIZE);
}
}
}
// First, run through all the types we can detect reliably based on
// magic numbers
uint32_t i;
for (i = 0; i < sSnifferEntryNum; ++i) {
if (mBufferLen >= sSnifferEntries[i].mByteLen && // enough data
memcmp(mBuffer, sSnifferEntries[i].mBytes, sSnifferEntries[i].mByteLen) == 0) { // and type matches
if (testDataLen >= sSnifferEntries[i].mByteLen && // enough data
memcmp(testData, sSnifferEntries[i].mBytes, sSnifferEntries[i].mByteLen) == 0) { // and type matches
NS_ASSERTION(sSnifferEntries[i].mMimeType ||
sSnifferEntries[i].mContentTypeSniffer,
"Must have either a type string or a function to set the type");
@ -342,7 +416,7 @@ void nsUnknownDecoder::DetermineContentType(nsIRequest* aRequest)
}
NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
(const uint8_t*)mBuffer, mBufferLen, mContentType);
(const uint8_t*)testData, testDataLen, mContentType);
if (!mContentType.IsEmpty()) {
return;
}
@ -376,10 +450,18 @@ bool nsUnknownDecoder::SniffForHTML(nsIRequest* aRequest)
if (!AllowSniffing(aRequest)) {
return false;
}
// Now look for HTML.
const char* str = mBuffer;
const char* end = mBuffer + mBufferLen;
const char* str;
const char* end;
if (mDecodedData.IsEmpty()) {
str = mBuffer;
end = mBuffer + mBufferLen;
} else {
str = mDecodedData.get();
end = mDecodedData.get() + std::min(mDecodedData.Length(),
MAX_BUFFER_SIZE);
}
// skip leading whitespace
while (str != end && nsCRT::IsAsciiSpace(*str)) {
@ -493,38 +575,48 @@ bool nsUnknownDecoder::LastDitchSniff(nsIRequest* aRequest)
// All we can do now is try to guess whether this is text/plain or
// application/octet-stream
const char* testData;
uint32_t testDataLen;
if (mDecodedData.IsEmpty()) {
testData = mBuffer;
testDataLen = mBufferLen;
} else {
testData = mDecodedData.get();
testDataLen = std::min(mDecodedData.Length(), MAX_BUFFER_SIZE);
}
// First, check for a BOM. If we see one, assume this is text/plain
// in whatever encoding. If there is a BOM _and_ text we will
// always have at least 4 bytes in the buffer (since the 2-byte BOMs
// are for 2-byte encodings and the UTF-8 BOM is 3 bytes).
if (mBufferLen >= 4) {
const unsigned char* buf = (const unsigned char*)mBuffer;
if (testDataLen >= 4) {
const unsigned char* buf = (const unsigned char*)testData;
if ((buf[0] == 0xFE && buf[1] == 0xFF) || // UTF-16, Big Endian
(buf[0] == 0xFF && buf[1] == 0xFE) || // UTF-16 or UCS-4, Little Endian
(buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) || // UTF-8
(buf[0] == 0 && buf[1] == 0 && buf[2] == 0xFE && buf[3] == 0xFF)) { // UCS-4, Big Endian
mContentType = TEXT_PLAIN;
return true;
}
}
// Now see whether the buffer has any non-text chars. If not, then let's
// just call it text/plain...
//
uint32_t i;
for (i = 0; i < mBufferLen && IS_TEXT_CHAR(mBuffer[i]); i++) {
for (i = 0; i < testDataLen && IS_TEXT_CHAR(testData[i]); i++) {
continue;
}
if (i == mBufferLen) {
if (i == testDataLen) {
mContentType = TEXT_PLAIN;
}
else {
mContentType = APPLICATION_OCTET_STREAM;
}
return true;
return true;
}
@ -562,6 +654,18 @@ nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
// Fire the OnStartRequest(...)
rv = mNextListener->OnStartRequest(request, aCtxt);
if (NS_SUCCEEDED(rv)) {
// install stream converter if required
nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(request);
if (encodedChannel) {
nsCOMPtr<nsIStreamListener> listener;
rv = encodedChannel->DoApplyContentConversions(mNextListener, getter_AddRefs(listener), aCtxt);
if (NS_SUCCEEDED(rv) && listener) {
mNextListener = listener;
}
}
}
if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY;
// If the request was canceled, then we need to treat that equivalently
@ -600,6 +704,51 @@ nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
return rv;
}
nsresult
nsUnknownDecoder::ConvertEncodedData(nsIRequest* request,
const char* data,
uint32_t length)
{
nsresult rv = NS_OK;
mDecodedData = "";
nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(request));
if (encodedChannel) {
nsRefPtr<ConvertedStreamListener> strListener =
new ConvertedStreamListener(this);
nsCOMPtr<nsIStreamListener> listener;
rv = encodedChannel->DoApplyContentConversions(strListener,
getter_AddRefs(listener),
nullptr);
if (NS_FAILED(rv)) {
return rv;
}
if (listener) {
listener->OnStartRequest(request, nullptr);
nsCOMPtr<nsIStringInputStream> rawStream =
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
if (!rawStream)
return NS_ERROR_FAILURE;
rv = rawStream->SetData((const char*)data, length);
NS_ENSURE_SUCCESS(rv, rv);
rv = listener->OnDataAvailable(request, nullptr, rawStream, 0,
length);
NS_ENSURE_SUCCESS(rv, rv);
listener->OnStopRequest(request, nullptr, NS_OK);
}
}
return rv;
}
void
nsBinaryDetector::DetermineContentType(nsIRequest* aRequest)
{

View File

@ -47,6 +47,26 @@ protected:
virtual void DetermineContentType(nsIRequest* aRequest);
nsresult FireListenerNotifications(nsIRequest* request, nsISupports *aCtxt);
class ConvertedStreamListener: public nsIStreamListener
{
public:
ConvertedStreamListener(nsUnknownDecoder *aDecoder);
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
private:
virtual ~ConvertedStreamListener();
static NS_METHOD AppendDataToString(nsIInputStream* inputStream,
void* closure,
const char* rawSegment,
uint32_t toOffset,
uint32_t count,
uint32_t* writeCount);
nsUnknownDecoder *mDecoder;
};
protected:
nsCOMPtr<nsIStreamListener> mNextListener;
@ -106,6 +126,10 @@ protected:
nsCString mContentType;
protected:
nsresult ConvertEncodedData(nsIRequest* request, const char* data,
uint32_t length);
nsCString mDecodedData; // If data are encoded this will be uncompress data.
};
#define NS_BINARYDETECTOR_CID \

View File

@ -7,27 +7,44 @@
Cu.import("resource://testing-common/httpd.js");
var httpserver = new HttpServer();
var testpath = "/simple";
var testpath = "/simple_plainText";
var httpbody = "<html><body>omg hai</body></html>";
var testpathGZip = "/simple_gzip";
//this is compressed httpbody;
var httpbodyGZip = ["0x1f", "0x8b", "0x8", "0x0", "0x0", "0x0", "0x0", "0x0",
"0x0", "0x3", "0xb3", "0xc9", "0x28", "0xc9", "0xcd", "0xb1",
"0xb3", "0x49", "0xca", "0x4f", "0xa9", "0xb4", "0xcb",
"0xcf", "0x4d", "0x57", "0xc8", "0x48", "0xcc", "0xb4",
"0xd1", "0x7", "0xf3", "0x6c", "0xf4", "0xc1", "0x52", "0x0",
"0x4", "0x99", "0x79", "0x2b", "0x21", "0x0", "0x0", "0x0"];
var buffer = "";
var dbg=0
if (dbg) { print("============== START =========="); }
function run_test() {
setup_test();
do_test_pending();
}
function setup_test() {
if (dbg) { print("============== setup_test: in"); }
httpserver.registerPathHandler(testpath, serverHandler);
add_test(function test_plainText() {
if (dbg) { print("============== test_plainText: in"); }
httpserver.registerPathHandler(testpath, serverHandler_plainText);
httpserver.start(-1);
var channel = setupChannel(testpath);
// ChannelListener defined in head_channels.js
channel.asyncOpen(new ChannelListener(checkRequest, channel), null);
if (dbg) { print("============== setup_test: out"); }
}
do_test_pending();
if (dbg) { print("============== test_plainText: out"); }
});
add_test(function test_GZip() {
if (dbg) { print("============== test_GZip: in"); }
httpserver.registerPathHandler(testpathGZip, serverHandler_GZip);
httpserver.start(-1);
var channel = setupChannel(testpathGZip);
// ChannelListener defined in head_channels.js
channel.asyncOpen(new ChannelListener(checkRequest, channel,
CL_EXPECT_GZIP), null);
do_test_pending();
if (dbg) { print("============== test_GZip: out"); }
});
function setupChannel(path) {
var ios = Cc["@mozilla.org/network/io-service;1"].
@ -39,12 +56,24 @@ function setupChannel(path) {
return chan;
}
function serverHandler(metadata, response) {
if (dbg) { print("============== serverHandler: in"); }
function serverHandler_plainText(metadata, response) {
if (dbg) { print("============== serverHandler plainText: in"); }
// no content type set
// response.setHeader("Content-Type", "text/plain", false);
response.bodyOutputStream.write(httpbody, httpbody.length);
if (dbg) { print("============== serverHandler: out"); }
if (dbg) { print("============== serverHandler plainText: out"); }
}
function serverHandler_GZip(metadata, response) {
if (dbg) { print("============== serverHandler GZip: in"); }
// no content type set
// response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Content-Encoding", "gzip", false);
var bos = Cc["@mozilla.org/binaryoutputstream;1"]
.createInstance(Ci.nsIBinaryOutputStream);
bos.setOutputStream(response.bodyOutputStream);
bos.writeByteArray(httpbodyGZip, httpbodyGZip.length);
if (dbg) { print("============== serverHandler GZip: out"); }
}
function checkRequest(request, data, context) {
@ -52,6 +81,10 @@ function checkRequest(request, data, context) {
do_check_eq(data, httpbody);
do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType,"text/html");
httpserver.stop(do_test_finished);
run_next_test();
if (dbg) { print("============== checkRequest: out"); }
}
function run_test() {
run_next_test();
}