From 37ea372325a9d2bd18008d86c0bc483c0942cbb3 Mon Sep 17 00:00:00 2001 From: "cbiesinger%web.de" Date: Sat, 4 Mar 2006 00:27:20 +0000 Subject: [PATCH] bug 324985 Allow components to register in a category to get told about all HTTP page loads and allow sniffing the content type. Only works for docshell-initiated loads. r=darin sr=bz a=darin git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@191747 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/docshell/base/nsDocShell.cpp | 5 +- mozilla/netwerk/base/public/nsIChannel.idl | 11 +- mozilla/netwerk/base/src/nsIOService.cpp | 1 + mozilla/netwerk/base/src/nsIOService.h | 8 ++ .../netwerk/base/src/nsInputStreamPump.cpp | 24 ++++ mozilla/netwerk/base/src/nsInputStreamPump.h | 9 ++ mozilla/netwerk/build/nsNetCID.h | 21 ++- .../protocol/http/src/nsHttpChannel.cpp | 49 ++++++- .../netwerk/protocol/http/src/nsHttpChannel.h | 8 +- mozilla/netwerk/test/Makefile.in | 1 + .../netwerk/test/unit/test_content_sniffer.js | 122 ++++++++++++++++++ 11 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 mozilla/netwerk/test/unit/test_content_sniffer.js diff --git a/mozilla/docshell/base/nsDocShell.cpp b/mozilla/docshell/base/nsDocShell.cpp index dc9b02e7e07..03efd0736d5 100644 --- a/mozilla/docshell/base/nsDocShell.cpp +++ b/mozilla/docshell/base/nsDocShell.cpp @@ -6786,10 +6786,11 @@ nsresult nsDocShell::DoChannelLoad(nsIChannel * aChannel, nsIURILoader * aURILoader) { nsresult rv; - // Mark the channel as being a document URI... + // Mark the channel as being a document URI and allow content sniffing... nsLoadFlags loadFlags = 0; (void) aChannel->GetLoadFlags(&loadFlags); - loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI | + nsIChannel::LOAD_CALL_CONTENT_SNIFFERS; // Load attributes depend on load type... switch (mLoadType) { diff --git a/mozilla/netwerk/base/public/nsIChannel.idl b/mozilla/netwerk/base/public/nsIChannel.idl index aecbd18ea16..5b3cf3a85ff 100644 --- a/mozilla/netwerk/base/public/nsIChannel.idl +++ b/mozilla/netwerk/base/public/nsIChannel.idl @@ -182,7 +182,7 @@ interface nsIChannel : nsIRequest /************************************************************************** * Channel specific load flags: * - * Bits 21-31 are reserved for future use by this interface or one of its + * Bits 22-31 are reserved for future use by this interface or one of its * derivatives (e.g., see nsICachingChannel). */ @@ -216,4 +216,13 @@ interface nsIChannel : nsIRequest * for this load has been determined. */ const unsigned long LOAD_TARGETED = 1 << 20; + + /** + * If this flag is set, the channel should call the content sniffers as + * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY. + * + * Note: Channels may ignore this flag; however, new channel implementations + * should only do so with good reason. + */ + const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21; }; diff --git a/mozilla/netwerk/base/src/nsIOService.cpp b/mozilla/netwerk/base/src/nsIOService.cpp index 5ced489867b..97298d996b7 100644 --- a/mozilla/netwerk/base/src/nsIOService.cpp +++ b/mozilla/netwerk/base/src/nsIOService.cpp @@ -155,6 +155,7 @@ nsIOService::nsIOService() : mOffline(PR_FALSE) , mOfflineForProfileChange(PR_FALSE) , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY) + , mContentSniffers(NS_CONTENT_SNIFFER_CATEGORY) { // Get the allocator ready if (!gBufferCache) diff --git a/mozilla/netwerk/base/src/nsIOService.h b/mozilla/netwerk/base/src/nsIOService.h index de76524b541..1eb1a4822fb 100644 --- a/mozilla/netwerk/base/src/nsIOService.h +++ b/mozilla/netwerk/base/src/nsIOService.h @@ -56,6 +56,7 @@ #include "nsWeakReference.h" #include "nsINetUtil.h" #include "nsIChannelEventSink.h" +#include "nsIContentSniffer.h" #include "nsCategoryCache.h" #define NS_N(x) (sizeof(x)/sizeof(*x)) @@ -99,6 +100,12 @@ public: nsresult OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan, PRUint32 flags); + // Gets the array of registered content sniffers + const nsCOMArray& + GetContentSniffers() const { + return mContentSniffers.GetEntries(); + } + private: // These shouldn't be called directly: // - construct using GetInstance @@ -131,6 +138,7 @@ private: // cached categories nsCategoryCache mChannelEventSinks; + nsCategoryCache mContentSniffers; nsVoidArray mRestrictedPortList; diff --git a/mozilla/netwerk/base/src/nsInputStreamPump.cpp b/mozilla/netwerk/base/src/nsInputStreamPump.cpp index a6869e38857..7a7dd29aa6a 100644 --- a/mozilla/netwerk/base/src/nsInputStreamPump.cpp +++ b/mozilla/netwerk/base/src/nsInputStreamPump.cpp @@ -83,6 +83,30 @@ nsInputStreamPump::~nsInputStreamPump() { } +nsresult +nsInputStreamPump::Create(nsInputStreamPump **result, + nsIInputStream *stream, + PRInt64 streamPos, + PRInt64 streamLen, + PRUint32 segsize, + PRUint32 segcount, + PRBool closeWhenDone) +{ + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + nsRefPtr pump = new nsInputStreamPump(); + if (pump) { + rv = pump->Init(stream, streamPos, streamLen, + segsize, segcount, closeWhenDone); + if (NS_SUCCEEDED(rv)) { + *result = nsnull; + pump.swap(*result); + } + } + return rv; +} + + + struct PeekData { PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure) : mFunc(fun), mClosure(closure) {} diff --git a/mozilla/netwerk/base/src/nsInputStreamPump.h b/mozilla/netwerk/base/src/nsInputStreamPump.h index ba691afff51..d50668c834a 100644 --- a/mozilla/netwerk/base/src/nsInputStreamPump.h +++ b/mozilla/netwerk/base/src/nsInputStreamPump.h @@ -62,6 +62,15 @@ public: nsInputStreamPump(); ~nsInputStreamPump(); + static NS_HIDDEN_(nsresult) + Create(nsInputStreamPump **result, + nsIInputStream *stream, + PRInt64 streamPos = -1, + PRInt64 streamLen = -1, + PRUint32 segsize = 0, + PRUint32 segcount = 0, + PRBool closeWhenDone = PR_FALSE); + typedef void (*PeekSegmentFun)(void *closure, const PRUint8 *buf, PRUint32 bufLen); /** diff --git a/mozilla/netwerk/build/nsNetCID.h b/mozilla/netwerk/build/nsNetCID.h index 69f2f6eb196..d46831ecfea 100644 --- a/mozilla/netwerk/build/nsNetCID.h +++ b/mozilla/netwerk/build/nsNetCID.h @@ -764,5 +764,24 @@ * contract ID of the service. */ #define NS_CHANNEL_EVENT_SINK_CATEGORY "net-channel-event-sinks" - + +/** + * Services in this category will get told about each load that happens and get + * the opportunity to override the detected MIME type via + * nsIContentSniffer_MOZILLA_1_8_BRANCH. + * Services should not set the MIME type on the channel directly, but return the + * new type. If getMIMETypeFromContent throws an exception, the type will remain + * unchanged. + * + * Note that only channels with the LOAD_CALL_CONTENT_SNIFFERS flag will call + * content sniffers. Also note that there can be security implications about + * changing the MIME type -- proxies filtering responses based on their MIME + * type might consider certain types to be safe, which these sniffers can + * override. + * + * Not all channels may implement content sniffing. See also + * nsIChannel::LOAD_CALL_CONTENT_SNIFFERS. + */ +#define NS_CONTENT_SNIFFER_CATEGORY "net-content-sniffers" + #endif // nsNetCID_h__ diff --git a/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp b/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp index 7bfffdb644a..c810d1db0bd 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -72,6 +72,7 @@ #include "nsInt64.h" #include "nsIVariant.h" #include "nsChannelProperties.h" +#include "nsIOService.h" // True if the local cache should be bypassed when processing a request. #define BYPASS_LOCAL_CACHE(loadFlags) \ @@ -654,8 +655,8 @@ nsHttpChannel::SetupTransaction() getter_AddRefs(responseStream)); if (NS_FAILED(rv)) return rv; - rv = NS_NewInputStreamPump(getter_AddRefs(mTransactionPump), - responseStream); + rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump), + responseStream); return rv; } @@ -718,6 +719,25 @@ nsHttpChannel::ApplyContentConversions() } } +static void +CallTypeSniffers(void *aClosure, const PRUint8 *aData, PRUint32 aCount) +{ + nsIChannel *chan = NS_STATIC_CAST(nsIChannel*, aClosure); + + const nsCOMArray& sniffers = + gIOService->GetContentSniffers(); + PRUint32 length = sniffers.Count(); + for (PRUint32 i = 0; i < length; ++i) { + nsCAutoString newType; + nsresult rv = + sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType); + if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) { + chan->SetContentType(newType); + break; + } + } +} + nsresult nsHttpChannel::CallOnStartRequest() { @@ -754,7 +774,18 @@ nsHttpChannel::CallOnStartRequest() if (mResponseHead) SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH, mResponseHead->ContentLength()); - + + // Allow consumers to override our content type + if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) && + gIOService->GetContentSniffers().Count() != 0) { + if (mTransactionPump) + mTransactionPump->PeekStream(CallTypeSniffers, + NS_STATIC_CAST(nsIChannel*, this)); + else + mCachePump->PeekStream(CallTypeSniffers, + NS_STATIC_CAST(nsIChannel*, this)); + } + LOG((" calling mListener->OnStartRequest\n")); nsresult rv = mListener->OnStartRequest(this, mListenerContext); if (NS_FAILED(rv)) return rv; @@ -1688,9 +1719,9 @@ nsHttpChannel::ReadFromCache() rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream)); if (NS_FAILED(rv)) return rv; - rv = NS_NewInputStreamPump(getter_AddRefs(mCachePump), - stream, nsInt64(-1), nsInt64(-1), 0, 0, - PR_TRUE); + rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), + stream, nsInt64(-1), nsInt64(-1), 0, 0, + PR_TRUE); if (NS_FAILED(rv)) return rv; return mCachePump->AsyncRead(this, mListenerContext); @@ -3989,6 +4020,12 @@ nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x status=%x]\n", this, request, mStatus)); + // Make sure things are what we expect them to be... + NS_ASSERTION(request == mCachePump || request == mTransactionPump, + "Unexpected request"); + NS_ASSERTION(!mTransactionPump || request == mTransactionPump, + "If we have a txn pump, request must be it"); + // don't enter this block if we're reading from the cache... if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) { // grab the security info from the connection object; the transaction diff --git a/mozilla/netwerk/protocol/http/src/nsHttpChannel.h b/mozilla/netwerk/protocol/http/src/nsHttpChannel.h index 1145884fbc0..b41e2efad3d 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpChannel.h +++ b/mozilla/netwerk/protocol/http/src/nsHttpChannel.h @@ -22,6 +22,7 @@ * * Contributor(s): * Darin Fisher (original author) + * Christian Biesinger * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -43,7 +44,9 @@ #include "nsHttpTransaction.h" #include "nsHttpRequestHead.h" #include "nsHttpAuthCache.h" +#include "nsInputStreamPump.h" #include "nsXPIDLString.h" +#include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsInt64.h" @@ -71,7 +74,6 @@ #include "nsIStringEnumerator.h" #include "nsIOutputStream.h" #include "nsIAsyncInputStream.h" -#include "nsIInputStreamPump.h" #include "nsIPrompt.h" #include "nsIResumableChannel.h" #include "nsISupportsPriority.h" @@ -227,7 +229,7 @@ private: nsHttpRequestHead mRequestHead; nsHttpResponseHead *mResponseHead; - nsCOMPtr mTransactionPump; + nsRefPtr mTransactionPump; nsHttpTransaction *mTransaction; // hard ref nsHttpConnectionInfo *mConnectionInfo; // hard ref @@ -245,7 +247,7 @@ private: // cache specific data nsCOMPtr mCacheEntry; - nsCOMPtr mCachePump; + nsRefPtr mCachePump; nsHttpResponseHead *mCachedResponseHead; nsCacheAccessMode mCacheAccess; PRUint32 mPostID; diff --git a/mozilla/netwerk/test/Makefile.in b/mozilla/netwerk/test/Makefile.in index 0bc740f0c9c..bee4aeed6b2 100644 --- a/mozilla/netwerk/test/Makefile.in +++ b/mozilla/netwerk/test/Makefile.in @@ -102,6 +102,7 @@ _UNIT_FILES = unit/test_all.sh \ unit/test_cookie_header.js \ unit/test_parse_content_type.js \ unit/test_event_sink.js \ + unit/test_content_sniffer.js \ $(NULL) libs:: $(_UNIT_FILES) $(INSTALL) $^ $(DIST)/bin/necko_unit_tests diff --git a/mozilla/netwerk/test/unit/test_content_sniffer.js b/mozilla/netwerk/test/unit/test_content_sniffer.js new file mode 100644 index 00000000000..1227fe2129e --- /dev/null +++ b/mozilla/netwerk/test/unit/test_content_sniffer.js @@ -0,0 +1,122 @@ +// This file tests nsIContentSniffer, introduced in bug 324985 + +const unknownType = "application/x-unknown-content-type"; +const sniffedType = "application/x-sniffed"; + +const snifferCID = Components.ID("{4c93d2db-8a56-48d7-b261-9cf2a8d998eb}"); +const snifferContract = "@mozilla.org/network/unittest/contentsniffer;1"; +const categoryName = "net-content-sniffers"; + +var sniffing_enabled = true; + +/** + * This object is both a factory and an nsIContentSniffer implementation (so, it + * is de-facto a service) + */ +var sniffer = { + QueryInterface: function sniffer_qi(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIContentSniffer_MOZILLA_1_8_BRANCH)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + createInstance: function sniffer_ci(outer, iid) { + if (outer) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return this.QueryInterface(iid); + }, + lockFactory: function sniffer_lockf(lock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + getMIMETypeFromContent: function (request, data, length) { + return sniffedType; + } +}; + +var listener = { + onStartRequest: function test_onStartR(request, ctx) { + try { + var chan = request.QueryInterface(Components.interfaces.nsIChannel); + if (chan.contentType == unknownType) + do_throw("Type should not be unknown!"); + if (sniffing_enabled && this._iteration > 2 && + chan.contentType != sniffedType) { + do_throw("Expecting <" + sniffedType +"> but got <" + + chan.contentType + "> for " + chan.URI.spec); + } else if (!sniffing_enabled && chan.contentType == sniffedType) { + do_throw("Sniffing not enabled but sniffer called for " + chan.URI.spec); + } + } catch (e) { + do_throw("Unexpected exception: " + e); + } + + throw Components.results.NS_ERROR_ABORT; + }, + + onDataAvailable: function test_ODA() { + throw Components.results.NS_ERROR_UNEXPECTED; + }, + + onStopRequest: function test_onStopR(request, ctx, status) { + run_test_iteration(this._iteration); + do_test_finished(); + }, + + _iteration: 1 +}; + +function makeChan(url) { + var ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + var chan = ios.newChannel(url, null, null); + if (sniffing_enabled) + chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; + + return chan; +} + +var urls = [ + // NOTE: First URL here runs without our content sniffer + // NOTE: Only test HTTP on the 1.8 branch, unimplemented for the rest. + "http://localhost:4444", + "http://localhost:4444" +]; + +function run_test() { + start_server(4444); + + Components.manager.nsIComponentRegistrar.registerFactory(snifferCID, + "Unit test content sniffer", snifferContract, sniffer); + + run_test_iteration(1); +} + +function run_test_iteration(index) { + if (index > urls.length) { + if (sniffing_enabled) { + sniffing_enabled = false; + index = listener._iteration = 1; + } else { + return; // we're done + } + } + + if (sniffing_enabled && index == 2) { + // Register our sniffer only here + // This also makes sure that dynamic registration is working + var catMan = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test", + snifferContract, false, true); + } + + var chan = makeChan(urls[index - 1]); + + listener._iteration++; + chan.asyncOpen(listener, null); + + do_test_pending(); +} +