diff --git a/netwerk/base/public/moz.build b/netwerk/base/public/moz.build index 7410107537c..1f559254a79 100644 --- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -78,6 +78,7 @@ XPIDL_SOURCES += [ 'nsIProxyInfo.idl', 'nsIRandomGenerator.idl', 'nsIRedirectChannelRegistrar.idl', + 'nsIRedirectHistory.idl', 'nsIRedirectResultListener.idl', 'nsIRequest.idl', 'nsIRequestObserver.idl', diff --git a/netwerk/base/public/nsIRedirectHistory.idl b/netwerk/base/public/nsIRedirectHistory.idl new file mode 100644 index 00000000000..4c59e7e7cc6 --- /dev/null +++ b/netwerk/base/public/nsIRedirectHistory.idl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/** + * Allows keeping track of channel redirects. Currently nsHttpChannel is the + * only implementor. + */ +#include "nsISupports.idl" + +interface nsIArray; +interface nsIPrincipal; + +[scriptable, uuid(ab87eabf-d0c4-40a9-b4b2-a1191108d4c0)] +interface nsIRedirectHistory : nsISupports +{ + /** + * An array of nsIPrincipal that store the redirects associated with this + * channel. This array is filled whether or not the channel has ever been + * opened. The last element of the array is associated with the most recent + * channel. + */ + readonly attribute nsIArray redirects; +}; diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index ef3f54d865c..34e1037beaa 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -19,6 +19,7 @@ #include "nsITimedChannel.h" #include "nsIEncodedChannel.h" #include "nsIApplicationCacheChannel.h" +#include "nsIMutableArray.h" #include "nsEscape.h" #include "nsStreamListenerWrapper.h" #include "nsISecurityConsoleMessage.h" @@ -160,6 +161,7 @@ NS_INTERFACE_MAP_BEGIN(HttpBaseChannel) NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel) NS_INTERFACE_MAP_ENTRY(nsIHttpChannel) NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal) + NS_INTERFACE_MAP_ENTRY(nsIRedirectHistory) NS_INTERFACE_MAP_ENTRY(nsIUploadChannel) NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2) NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) @@ -1588,6 +1590,13 @@ HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::AddRedirect(nsIPrincipal *aRedirect) +{ + mRedirects.AppendObject(aRedirect); + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsISupportsPriority //----------------------------------------------------------------------------- @@ -1654,6 +1663,60 @@ HttpBaseChannel::GetEntityID(nsACString& aEntityID) return NS_OK; } +nsIPrincipal * +HttpBaseChannel::GetPrincipal(bool requireAppId) +{ + if (mPrincipal) { + if (requireAppId && mPrincipal->GetUnknownAppId()) { + LOG(("HttpBaseChannel::GetPrincipal: No app id [this=%p]", this)); + return nullptr; + } + return mPrincipal; + } + + nsIScriptSecurityManager *securityManager = + nsContentUtils::GetSecurityManager(); + + if (!securityManager) { + LOG(("HttpBaseChannel::GetPrincipal: No security manager [this=%p]", + this)); + return nullptr; + } + + securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal)); + if (!mPrincipal) { + LOG(("HttpBaseChannel::GetPrincipal: No channel principal [this=%p]", + this)); + return nullptr; + } + + // principals with unknown app ids do not work with the permission manager + if (requireAppId && mPrincipal->GetUnknownAppId()) { + LOG(("HttpBaseChannel::GetPrincipal: No app id [this=%p]", this)); + return nullptr; + } + + return mPrincipal; +} + +// nsIRedirectHistory +NS_IMETHODIMP +HttpBaseChannel::GetRedirects(nsIArray * *aRedirects) +{ + nsresult rv; + nsCOMPtr redirects = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < mRedirects.Count(); ++i) { + rv = redirects->AppendElement(mRedirects[i], false); + NS_ENSURE_SUCCESS(rv, rv); + } + *aRedirects = redirects; + NS_IF_ADDREF(*aRedirects); + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel::nsITraceableChannel //----------------------------------------------------------------------------- @@ -1899,6 +1962,25 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, "[this=%p] transferring chain of redirect cache-keys", this)); httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget()); } + // Transfer existing redirect information. Add all of our existing + // redirects to the new channel. + for (int32_t i = 0; i < mRedirects.Count(); ++i) { +#ifdef PR_LOGGING + nsCOMPtr uri; + mRedirects[i]->GetURI(getter_AddRefs(uri)); + nsCString spec; + uri->GetSpec(spec); + LOG(("HttpBaseChannel::SetupReplacementChannel adding redirect %s " + "[this=%p]", spec.get(), this)); +#endif + httpInternal->AddRedirect(mRedirects[i]); + } + + // Add our own principal to the redirect information on the new channel. If + // the redirect is vetoed, then newChannel->AsyncOpen won't be called. + // However, the new channel's redirect chain will still be complete. + nsCOMPtr principal = GetPrincipal(false); + httpInternal->AddRedirect(principal); } // transfer application cache information diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index e177a393e85..fe799ef3816 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -19,6 +19,7 @@ #include "nsIHttpChannel.h" #include "nsHttpHandler.h" #include "nsIHttpChannelInternal.h" +#include "nsIRedirectHistory.h" #include "nsIUploadChannel.h" #include "nsIUploadChannel2.h" #include "nsIProgressEventSink.h" @@ -53,6 +54,7 @@ class HttpBaseChannel : public nsHashPropertyBag , public nsIEncodedChannel , public nsIHttpChannel , public nsIHttpChannelInternal + , public nsIRedirectHistory , public nsIUploadChannel , public nsIUploadChannel2 , public nsISupportsPriority @@ -67,6 +69,7 @@ public: NS_DECL_NSIUPLOADCHANNEL2 NS_DECL_NSITRACEABLECHANNEL NS_DECL_NSITIMEDCHANNEL + NS_DECL_NSIREDIRECTHISTORY HttpBaseChannel(); virtual ~HttpBaseChannel(); @@ -161,6 +164,7 @@ public: NS_IMETHOD TakeAllSecurityMessages(nsCOMArray &aMessages); NS_IMETHOD GetResponseTimeoutEnabled(bool *aEnable); NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable); + NS_IMETHOD AddRedirect(nsIPrincipal *aRedirect); inline void CleanRedirectCacheChainIfNecessary() { @@ -215,7 +219,7 @@ public: /* Necko internal use only... */ nsHttpRequestHead::ParsedMethodType method); protected: - nsCOMArray mSecurityConsoleMessages; + nsCOMArray mSecurityConsoleMessages; // Handle notifying listener, removing from loadgroup if request failed. void DoNotifyListener(); @@ -250,6 +254,11 @@ protected: // Checks whether or not aURI and mOriginalURI share the same domain. bool SameOriginWithOriginalUri(nsIURI *aURI); + // GetPrincipal + // Returns the channel principal. If requireAppId is true, then returns + // null if the principal has unknown appId. + nsIPrincipal *GetPrincipal(bool requireAppId); + friend class PrivateBrowsingChannel; nsCOMPtr mURI; @@ -321,6 +330,8 @@ protected: nsCOMPtr mAPIRedirectToURI; nsAutoPtr > mRedirectedCachekeys; + // Redirects added by previous channels. + nsCOMArray mRedirects; uint32_t mProxyResolveFlags; nsCOMPtr mProxyURI; @@ -351,6 +362,8 @@ protected: // copied from the transaction before we null out mTransaction // so that the timing can still be queried from OnStopRequest TimingStruct mTransactionTimings; + + nsCOMPtr mPrincipal; }; // Share some code while working around C++'s absurd inability to handle casting diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 34cd8fc8929..1941b98d032 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -613,7 +613,7 @@ nsHttpChannel::RetrieveSSLOptions() if (!IsHTTPS() || mPrivateBrowsing) return; - nsIPrincipal *principal = GetPrincipal(); + nsIPrincipal *principal = GetPrincipal(true); if (!principal) return; @@ -1172,7 +1172,7 @@ nsHttpChannel::ProcessSSLInformation() int16_t kea = ssl->GetKEAUsed(); - nsIPrincipal *principal = GetPrincipal(); + nsIPrincipal *principal = GetPrincipal(true); if (!principal) return; @@ -4235,7 +4235,8 @@ nsHttpChannel::ContinueProcessRedirection(nsresult rv) { AutoRedirectVetoNotifier notifier(this); - LOG(("ContinueProcessRedirection [rv=%x]\n", rv)); + LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%x,this=%p]\n", rv, + this)); if (NS_FAILED(rv)) return rv; @@ -6131,30 +6132,6 @@ nsHttpChannel::UpdateAggregateCallbacks() mTransaction->SetSecurityCallbacks(callbacks); } -nsIPrincipal * -nsHttpChannel::GetPrincipal() -{ - if (mPrincipal) - return mPrincipal; - - nsIScriptSecurityManager *securityManager = - nsContentUtils::GetSecurityManager(); - - if (!securityManager) - return nullptr; - - securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal)); - if (!mPrincipal) - return nullptr; - - // principals with unknown app ids do not work with the permission manager - if (mPrincipal->GetUnknownAppId()) - mPrincipal = nullptr; - - return mPrincipal; -} - - NS_IMETHODIMP nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) { diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 159c4be408c..32458039e0b 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -424,8 +424,6 @@ private: // cache telemetry bool mDidReval; private: - nsIPrincipal *GetPrincipal(); - nsCOMPtr mPrincipal; bool mForcePending; }; diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 578fe84ebe8..5df2c56a84f 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -13,12 +13,13 @@ class nsCString; [ptr] native StringArray(nsTArray); [ref] native securityMessagesArray(nsCOMArray); -interface nsISocketTransport; interface nsIAsyncInputStream; interface nsIAsyncOutputStream; -interface nsIURI; +interface nsIPrincipal; interface nsIProxyInfo; interface nsISecurityConsoleMessage; +interface nsISocketTransport; +interface nsIURI; /** * The callback interface for nsIHttpChannelInternal::HTTPUpgrade() @@ -37,7 +38,7 @@ interface nsIHttpUpgradeListener : nsISupports * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(b733194f-6751-4876-a444-bca4ba3f2fcb)] +[scriptable, uuid(a4bf4fc5-b5a9-4098-bd20-409d71bf18e6)] interface nsIHttpChannelInternal : nsISupports { /** @@ -192,4 +193,10 @@ interface nsIHttpChannelInternal : nsISupports * May return null when redirectTo() has not been called. */ readonly attribute nsIURI apiRedirectToURI; + + /** + * Add a new nsIPrincipal to the redirect chain. This is the only way to + * write to nsIRedirectHistory.redirects. + */ + void addRedirect(in nsIPrincipal aPrincipal); }; diff --git a/netwerk/test/unit/test_redirect_history.js b/netwerk/test/unit/test_redirect_history.js new file mode 100644 index 00000000000..12fec884f2b --- /dev/null +++ b/netwerk/test/unit/test_redirect_history.js @@ -0,0 +1,63 @@ +Cu.import("resource://testing-common/httpd.js"); + +XPCOMUtils.defineLazyGetter(this, "URL", function() { + return "http://localhost:" + httpServer.identity.primaryPort; +}); + +var httpServer = null; +var randomPath = "/redirect/" + Math.random(); +var redirects = []; +const numRedirects = 10; + +function make_channel(url, callback, ctx) { + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + return ios.newChannel(url, "", null); +} + +const responseBody = "response body"; + +function contentHandler(request, response) +{ + response.setHeader("Content-Type", "text/plain"); + response.bodyOutputStream.write(responseBody, responseBody.length); +} + +function finish_test(request, buffer) +{ + do_check_eq(buffer, responseBody); + let chan = request.QueryInterface(Ci.nsIRedirectHistory); + do_check_eq(numRedirects - 1, chan.redirects.length); + for (let i = 0; i < numRedirects - 1; ++i) { + let principal = chan.redirects.queryElementAt(i, Ci.nsIPrincipal); + do_check_eq(URL + redirects[i], principal.URI.spec); + } + httpServer.stop(do_test_finished); +} + +function redirectHandler(index, request, response) { + response.setStatusLine(request.httpVersion, 301, "Moved"); + let path = redirects[index + 1]; + response.setHeader("Location", URL + path, false); +} + +function run_test() +{ + httpServer = new HttpServer(); + for (let i = 0; i < numRedirects; ++i) { + var randomPath = "/redirect/" + Math.random(); + redirects.push(randomPath); + if (i < numRedirects - 1) { + httpServer.registerPathHandler(randomPath, redirectHandler.bind(this, i)); + } else { + // The last one doesn't redirect + httpServer.registerPathHandler(redirects[numRedirects - 1], + contentHandler); + } + } + httpServer.start(-1); + + var chan = make_channel(URL + redirects[0]); + chan.asyncOpen(new ChannelListener(finish_test, null), null); + do_test_pending(); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 4230f72ae5f..91d834568f8 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -324,3 +324,4 @@ skip-if = os == "android" [test_signature_extraction.js] run-if = os == "win" [test_udp_multicast.js] +[test_redirect_history.js] diff --git a/netwerk/test/unit_ipc/test_redirect_history_wrap.js b/netwerk/test/unit_ipc/test_redirect_history_wrap.js new file mode 100644 index 00000000000..38cdfa35ec0 --- /dev/null +++ b/netwerk/test/unit_ipc/test_redirect_history_wrap.js @@ -0,0 +1,7 @@ +// +// Run test script in content process instead of chrome (xpcshell's default) +// + +function run_test() { + run_test_in_child("../unit/test_redirect_history.js"); +} diff --git a/netwerk/test/unit_ipc/xpcshell.ini b/netwerk/test/unit_ipc/xpcshell.ini index 515e5433612..b8960975685 100644 --- a/netwerk/test/unit_ipc/xpcshell.ini +++ b/netwerk/test/unit_ipc/xpcshell.ini @@ -32,3 +32,4 @@ skip-if = true [test_simple_wrap.js] [test_xmlhttprequest_wrap.js] [test_XHR_redirects.js] +[test_redirect_history_wrap.js]