/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Jason Duell * Honza Bambas * * 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 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/net/HttpChannelParent.h" #include "mozilla/dom/TabParent.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/unused.h" #include "HttpChannelParentListener.h" #include "nsHttpChannel.h" #include "nsHttpHandler.h" #include "nsNetUtil.h" #include "nsISupportsPriority.h" #include "nsIAuthPromptProvider.h" #include "nsIDocShellTreeItem.h" #include "nsIBadCertListener2.h" #include "nsICacheEntryDescriptor.h" #include "nsSerializationHelper.h" #include "nsISerializable.h" #include "nsIAssociatedContentSecurity.h" #include "nsIApplicationCacheService.h" #include "nsIOfflineCacheUpdate.h" #include "nsIRedirectChannelRegistrar.h" #include "prinit.h" namespace mozilla { namespace net { HttpChannelParent::HttpChannelParent(PBrowserParent* iframeEmbedding) : mIPCClosed(false) , mStoredStatus(0) , mStoredProgress(0) , mStoredProgressMax(0) , mSentRedirect1Begin(false) , mSentRedirect1BeginFailed(false) , mReceivedRedirect2Verify(false) { // Ensure gHttpHandler is initialized: we need the atom table up and running. nsIHttpProtocolHandler* handler; CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &handler); NS_ASSERTION(handler, "no http handler"); mTabParent = do_QueryObject(static_cast(iframeEmbedding)); } HttpChannelParent::~HttpChannelParent() { gHttpHandler->Release(); } void HttpChannelParent::ActorDestroy(ActorDestroyReason why) { // We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest // yet, but child process has crashed. We must not try to send any more msgs // to child, or IPDL will kill chrome process, too. mIPCClosed = true; } //----------------------------------------------------------------------------- // HttpChannelParent::nsISupports //----------------------------------------------------------------------------- NS_IMPL_ISUPPORTS6(HttpChannelParent, nsIInterfaceRequestor, nsIProgressEventSink, nsIRequestObserver, nsIStreamListener, nsIParentChannel, nsIParentRedirectingChannel) //----------------------------------------------------------------------------- // HttpChannelParent::nsIInterfaceRequestor //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::GetInterface(const nsIID& aIID, void **result) { if (aIID.Equals(NS_GET_IID(nsIAuthPromptProvider)) || aIID.Equals(NS_GET_IID(nsISecureBrowserUI))) { if (!mTabParent) return NS_NOINTERFACE; return mTabParent->QueryInterface(aIID, result); } return QueryInterface(aIID, result); } //----------------------------------------------------------------------------- // HttpChannelParent::PHttpChannelParent //----------------------------------------------------------------------------- bool HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI, const IPC::URI& aOriginalURI, const IPC::URI& aDocURI, const IPC::URI& aReferrerURI, const PRUint32& loadFlags, const RequestHeaderTuples& requestHeaders, const nsHttpAtom& requestMethod, const IPC::InputStream& uploadStream, const bool& uploadStreamHasHeaders, const PRUint16& priority, const PRUint8& redirectionLimit, const bool& allowPipelining, const bool& forceAllowThirdPartyCookie, const bool& doResumeAt, const PRUint64& startPos, const nsCString& entityID, const bool& chooseApplicationCache, const nsCString& appCacheClientID, const bool& allowSpdy) { nsCOMPtr uri(aURI); nsCOMPtr originalUri(aOriginalURI); nsCOMPtr docUri(aDocURI); nsCOMPtr referrerUri(aReferrerURI); nsCString uriSpec; uri->GetSpec(uriSpec); LOG(("HttpChannelParent RecvAsyncOpen [this=%x uri=%s]\n", this, uriSpec.get())); nsresult rv; nsCOMPtr ios(do_GetIOService(&rv)); if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); rv = NS_NewChannel(getter_AddRefs(mChannel), uri, ios, nsnull, nsnull, loadFlags); if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); nsHttpChannel *httpChan = static_cast(mChannel.get()); if (doResumeAt) httpChan->ResumeAt(startPos, entityID); if (originalUri) httpChan->SetOriginalURI(originalUri); if (docUri) httpChan->SetDocumentURI(docUri); if (referrerUri) httpChan->SetReferrerInternal(referrerUri); if (loadFlags != nsIRequest::LOAD_NORMAL) httpChan->SetLoadFlags(loadFlags); for (PRUint32 i = 0; i < requestHeaders.Length(); i++) { httpChan->SetRequestHeader(requestHeaders[i].mHeader, requestHeaders[i].mValue, requestHeaders[i].mMerge); } nsRefPtr channelListener = new HttpChannelParentListener(this); httpChan->SetNotificationCallbacks(channelListener); httpChan->SetRequestMethod(nsDependentCString(requestMethod.get())); nsCOMPtr stream(uploadStream); if (stream) { httpChan->InternalSetUploadStream(stream); httpChan->SetUploadStreamHasHeaders(uploadStreamHasHeaders); } if (priority != nsISupportsPriority::PRIORITY_NORMAL) httpChan->SetPriority(priority); httpChan->SetRedirectionLimit(redirectionLimit); httpChan->SetAllowPipelining(allowPipelining); httpChan->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie); httpChan->SetAllowSpdy(allowSpdy); nsCOMPtr appCacheChan = do_QueryInterface(mChannel); nsCOMPtr appCacheService = do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID); bool setChooseApplicationCache = chooseApplicationCache; if (appCacheChan && appCacheService) { // We might potentially want to drop this flag (that is TRUE by default) // after we succefully associate the channel with an application cache // reported by the channel child. Dropping it here may be too early. appCacheChan->SetInheritApplicationCache(false); if (!appCacheClientID.IsEmpty()) { nsCOMPtr appCache; rv = appCacheService->GetApplicationCache(appCacheClientID, getter_AddRefs(appCache)); if (NS_SUCCEEDED(rv)) { appCacheChan->SetApplicationCache(appCache); setChooseApplicationCache = false; } } if (setChooseApplicationCache) { nsCOMPtr offlineUpdateService = do_GetService("@mozilla.org/offlinecacheupdate-service;1", &rv); if (NS_SUCCEEDED(rv)) { rv = offlineUpdateService->OfflineAppAllowedForURI(uri, nsnull, &setChooseApplicationCache); if (setChooseApplicationCache && NS_SUCCEEDED(rv)) appCacheChan->SetChooseApplicationCache(true); } } } rv = httpChan->AsyncOpen(channelListener, nsnull); if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); return true; } bool HttpChannelParent::RecvConnectChannel(const PRUint32& channelId) { nsresult rv; LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId)); rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(mChannel)); LOG((" found channel %p, rv=%08x", mChannel.get(), rv)); return true; } bool HttpChannelParent::RecvSetPriority(const PRUint16& priority) { if (mChannel) { nsHttpChannel *httpChan = static_cast(mChannel.get()); httpChan->SetPriority(priority); } nsCOMPtr priorityRedirectChannel = do_QueryInterface(mRedirectChannel); if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority); return true; } bool HttpChannelParent::RecvSuspend() { if (mChannel) { mChannel->Suspend(); } return true; } bool HttpChannelParent::RecvResume() { if (mChannel) { mChannel->Resume(); } return true; } bool HttpChannelParent::RecvCancel(const nsresult& status) { // May receive cancel before channel has been constructed! if (mChannel) { nsHttpChannel *httpChan = static_cast(mChannel.get()); httpChan->Cancel(status); } return true; } bool HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset) { if (mCacheDescriptor) mCacheDescriptor->SetMetaDataElement("charset", charset.get()); return true; } bool HttpChannelParent::RecvUpdateAssociatedContentSecurity(const PRInt32& high, const PRInt32& low, const PRInt32& broken, const PRInt32& no) { if (mAssociatedContentSecurity) { mAssociatedContentSecurity->SetCountSubRequestsHighSecurity(high); mAssociatedContentSecurity->SetCountSubRequestsLowSecurity(low); mAssociatedContentSecurity->SetCountSubRequestsBrokenSecurity(broken); mAssociatedContentSecurity->SetCountSubRequestsNoSecurity(no); } return true; } // Bug 621446 investigation, we don't want conditional PR_Aborts bellow to be // merged to a single address. #pragma warning(disable : 4068) #pragma GCC optimize ("O0") bool HttpChannelParent::RecvRedirect2Verify(const nsresult& result, const RequestHeaderTuples& changedHeaders) { if (NS_SUCCEEDED(result)) { nsCOMPtr newHttpChannel = do_QueryInterface(mRedirectChannel); if (newHttpChannel) { for (PRUint32 i = 0; i < changedHeaders.Length(); i++) { newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader, changedHeaders[i].mValue, changedHeaders[i].mMerge); } } } if (!mRedirectCallback) { // Bug 621446 investigation (optimization turned off above) if (mReceivedRedirect2Verify) ::PR_Abort(); if (mSentRedirect1BeginFailed) ::PR_Abort(); if (mSentRedirect1Begin && NS_FAILED(result)) ::PR_Abort(); if (mSentRedirect1Begin && NS_SUCCEEDED(result)) ::PR_Abort(); if (!mRedirectChannel) ::PR_Abort(); } mReceivedRedirect2Verify = true; mRedirectCallback->OnRedirectVerifyCallback(result); mRedirectCallback = nsnull; return true; } // Bug 621446 investigation #pragma GCC reset_options bool HttpChannelParent::RecvDocumentChannelCleanup() { // From now on only using mAssociatedContentSecurity. Free everything else. mChannel = 0; // Reclaim some memory sooner. mCacheDescriptor = 0; // Else we'll block other channels reading same URI return true; } bool HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign() { nsHttpChannel *httpChan = static_cast(mChannel.get()); httpChan->MarkOfflineCacheEntryAsForeign(); return true; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIRequestObserver //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) { LOG(("HttpChannelParent::OnStartRequest [this=%x]\n", this)); nsHttpChannel *chan = static_cast(aRequest); nsHttpResponseHead *responseHead = chan->GetResponseHead(); nsHttpRequestHead *requestHead = chan->GetRequestHead(); bool isFromCache = false; chan->IsFromCache(&isFromCache); PRUint32 expirationTime = nsICache::NO_EXPIRATION_TIME; chan->GetCacheTokenExpirationTime(&expirationTime); nsCString cachedCharset; chan->GetCacheTokenCachedCharset(cachedCharset); bool loadedFromApplicationCache; chan->GetLoadedFromApplicationCache(&loadedFromApplicationCache); if (loadedFromApplicationCache) { nsCOMPtr appCache; chan->GetApplicationCache(getter_AddRefs(appCache)); nsCString appCacheGroupId; nsCString appCacheClientId; appCache->GetGroupID(appCacheGroupId); appCache->GetClientID(appCacheClientId); if (mIPCClosed || !SendAssociateApplicationCache(appCacheGroupId, appCacheClientId)) { return NS_ERROR_UNEXPECTED; } } nsCOMPtr encodedChannel = do_QueryInterface(aRequest); if (encodedChannel) encodedChannel->SetApplyConversion(false); // Keep the cache entry for future use in RecvSetCacheTokenCachedCharset(). // It could be already released by nsHttpChannel at that time. chan->GetCacheToken(getter_AddRefs(mCacheDescriptor)); nsCString secInfoSerialization; nsCOMPtr secInfoSupp; chan->GetSecurityInfo(getter_AddRefs(secInfoSupp)); if (secInfoSupp) { mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); nsCOMPtr secInfoSer = do_QueryInterface(secInfoSupp); if (secInfoSer) NS_SerializeToString(secInfoSer, secInfoSerialization); } nsHttpChannel *httpChan = static_cast(mChannel.get()); if (mIPCClosed || !SendOnStartRequest(responseHead ? *responseHead : nsHttpResponseHead(), !!responseHead, requestHead->Headers(), isFromCache, mCacheDescriptor ? true : false, expirationTime, cachedCharset, secInfoSerialization, httpChan->GetSelfAddr(), httpChan->GetPeerAddr())) { return NS_ERROR_UNEXPECTED; } return NS_OK; } NS_IMETHODIMP HttpChannelParent::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode) { LOG(("HttpChannelParent::OnStopRequest: [this=%x status=%ul]\n", this, aStatusCode)); if (mIPCClosed || !SendOnStopRequest(aStatusCode)) return NS_ERROR_UNEXPECTED; return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIStreamListener //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, PRUint32 aOffset, PRUint32 aCount) { LOG(("HttpChannelParent::OnDataAvailable [this=%x]\n", this)); nsCString data; nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); if (NS_FAILED(rv)) return rv; // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set // mStoredStatus/mStoredProgress(Max) to appropriate values, unless // LOAD_BACKGROUND set. In that case, they'll have garbage values, but // child doesn't use them. if (mIPCClosed || !SendOnTransportAndData(mStoredStatus, mStoredProgress, mStoredProgressMax, data, aOffset, aCount)) { return NS_ERROR_UNEXPECTED; } return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIProgressEventSink //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::OnProgress(nsIRequest *aRequest, nsISupports *aContext, PRUint64 aProgress, PRUint64 aProgressMax) { // OnStatus has always just set mStoredStatus. If it indicates this precedes // OnDataAvailable, store and ODA will send to child. if (mStoredStatus == nsISocketTransport::STATUS_RECEIVING_FROM || mStoredStatus == nsITransport::STATUS_READING) { mStoredProgress = aProgress; mStoredProgressMax = aProgressMax; } else { // Send to child now. The only case I've observed that this handles (i.e. // non-ODA status with progress > 0) is data upload progress notification // (status == nsISocketTransport::STATUS_SENDING_TO) if (mIPCClosed || !SendOnProgress(aProgress, aProgressMax)) return NS_ERROR_UNEXPECTED; } return NS_OK; } NS_IMETHODIMP HttpChannelParent::OnStatus(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus, const PRUnichar *aStatusArg) { // If this precedes OnDataAvailable, store and ODA will send to child. if (aStatus == nsISocketTransport::STATUS_RECEIVING_FROM || aStatus == nsITransport::STATUS_READING) { mStoredStatus = aStatus; return NS_OK; } // Otherwise, send to child now if (mIPCClosed || !SendOnStatus(aStatus)) return NS_ERROR_UNEXPECTED; return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIParentChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::Delete() { if (!mIPCClosed) unused << SendDeleteSelf(); return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIParentRedirectingChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParent::StartRedirect(PRUint32 newChannelId, nsIChannel* newChannel, PRUint32 redirectFlags, nsIAsyncVerifyRedirectCallback* callback) { if (mIPCClosed) return NS_BINDING_ABORTED; nsCOMPtr newURI; newChannel->GetURI(getter_AddRefs(newURI)); nsHttpChannel *httpChan = static_cast(mChannel.get()); nsHttpResponseHead *responseHead = httpChan->GetResponseHead(); bool result = SendRedirect1Begin(newChannelId, IPC::URI(newURI), redirectFlags, responseHead ? *responseHead : nsHttpResponseHead()); if (!result) { // Bug 621446 investigation mSentRedirect1BeginFailed = true; return NS_BINDING_ABORTED; } // Bug 621446 investigation mSentRedirect1Begin = true; // Result is handled in RecvRedirect2Verify above mRedirectChannel = newChannel; mRedirectCallback = callback; return NS_OK; } NS_IMETHODIMP HttpChannelParent::CompleteRedirect(bool succeeded) { if (succeeded && !mIPCClosed) { // TODO: check return value: assume child dead if failed unused << SendRedirect3Complete(); } mRedirectChannel = nsnull; return NS_OK; } }} // mozilla::net