michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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 "nsURIChecker.h" michael@0: #include "nsIAuthPrompt.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsString.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static bool michael@0: ServerIsNES3x(nsIHttpChannel *httpChannel) michael@0: { michael@0: nsAutoCString server; michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Server"), server); michael@0: // case sensitive string comparison is OK here. the server string michael@0: // is a well-known value, so we should not have to worry about it michael@0: // being case-smashed or otherwise case-mutated. michael@0: return StringBeginsWith(server, michael@0: NS_LITERAL_CSTRING("Netscape-Enterprise/3.")); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsURIChecker, michael@0: nsIURIChecker, michael@0: nsIRequest, michael@0: nsIRequestObserver, michael@0: nsIStreamListener, michael@0: nsIChannelEventSink, michael@0: nsIInterfaceRequestor) michael@0: michael@0: nsURIChecker::nsURIChecker() michael@0: : mStatus(NS_OK) michael@0: , mIsPending(false) michael@0: , mAllowHead(true) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsURIChecker::SetStatusAndCallBack(nsresult aStatus) michael@0: { michael@0: mStatus = aStatus; michael@0: mIsPending = false; michael@0: michael@0: if (mObserver) { michael@0: mObserver->OnStartRequest(this, mObserverContext); michael@0: mObserver->OnStopRequest(this, mObserverContext, mStatus); michael@0: mObserver = nullptr; michael@0: mObserverContext = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsURIChecker::CheckStatus() michael@0: { michael@0: NS_ASSERTION(mChannel, "no channel"); michael@0: michael@0: nsresult status; michael@0: nsresult rv = mChannel->GetStatus(&status); michael@0: // DNS errors and other obvious problems will return failure status michael@0: if (NS_FAILED(rv) || NS_FAILED(status)) michael@0: return NS_BINDING_FAILED; michael@0: michael@0: // If status is zero, it might still be an error if it's http: michael@0: // http has data even when there's an error like a 404. michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel); michael@0: if (!httpChannel) michael@0: return NS_BINDING_SUCCEEDED; michael@0: michael@0: uint32_t responseStatus; michael@0: rv = httpChannel->GetResponseStatus(&responseStatus); michael@0: if (NS_FAILED(rv)) michael@0: return NS_BINDING_FAILED; michael@0: michael@0: // If it's between 200-299, it's valid: michael@0: if (responseStatus / 100 == 2) michael@0: return NS_BINDING_SUCCEEDED; michael@0: michael@0: // If we got a 404 (not found), we need some extra checking: michael@0: // toplevel urls from Netscape Enterprise Server 3.6, like the old AOL- michael@0: // hosted http://www.mozilla.org, generate a 404 and will have to be michael@0: // retried without the head. michael@0: if (responseStatus == 404) { michael@0: if (mAllowHead && ServerIsNES3x(httpChannel)) { michael@0: mAllowHead = false; michael@0: michael@0: // save the current value of mChannel in case we can't issue michael@0: // the new request for some reason. michael@0: nsCOMPtr lastChannel = mChannel; michael@0: michael@0: nsCOMPtr uri; michael@0: uint32_t loadFlags; michael@0: michael@0: rv = lastChannel->GetOriginalURI(getter_AddRefs(uri)); michael@0: nsresult tmp = lastChannel->GetLoadFlags(&loadFlags); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: michael@0: // XXX we are carrying over the load flags, but what about other michael@0: // parameters that may have been set on lastChannel?? michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = Init(uri); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mChannel->SetLoadFlags(loadFlags); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = AsyncCheck(mObserver, mObserverContext); michael@0: // if we succeeded in loading the new channel, then we michael@0: // want to return without notifying our observer. michael@0: if (NS_SUCCEEDED(rv)) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: } michael@0: } michael@0: // it is important to update this so our observer will be able michael@0: // to access our baseChannel attribute if they want. michael@0: mChannel = lastChannel; michael@0: } michael@0: } michael@0: michael@0: // If we get here, assume the resource does not exist. michael@0: return NS_BINDING_FAILED; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIURIChecker methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::Init(nsIURI *aURI) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr ios = do_GetIOService(&rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = ios->NewChannelFromURI(aURI, getter_AddRefs(mChannel)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (mAllowHead) { michael@0: mAllowHead = false; michael@0: // See if it's an http channel, which needs special treatment: michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel); michael@0: if (httpChannel) { michael@0: // We can have an HTTP channel that has a non-HTTP URL if michael@0: // we're doing FTP via an HTTP proxy, for example. See for michael@0: // example bug 148813 michael@0: bool isReallyHTTP = false; michael@0: aURI->SchemeIs("http", &isReallyHTTP); michael@0: if (!isReallyHTTP) michael@0: aURI->SchemeIs("https", &isReallyHTTP); michael@0: if (isReallyHTTP) { michael@0: httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD")); michael@0: // set back to true so we'll know that this request is issuing michael@0: // a HEAD request. this is used down in OnStartRequest to michael@0: // handle cases where we need to repeat the request as a normal michael@0: // GET to deal with server borkage. michael@0: mAllowHead = true; michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::AsyncCheck(nsIRequestObserver *aObserver, michael@0: nsISupports *aObserverContext) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Hook us up to listen to redirects and the like (this creates a reference michael@0: // cycle!) michael@0: mChannel->SetNotificationCallbacks(this); michael@0: michael@0: // and start the request: michael@0: nsresult rv = mChannel->AsyncOpen(this, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: mChannel = nullptr; michael@0: else { michael@0: // ok, wait for OnStartRequest to fire. michael@0: mIsPending = true; michael@0: mObserver = aObserver; michael@0: mObserverContext = aObserverContext; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetBaseChannel(nsIChannel **aChannel) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: NS_ADDREF(*aChannel = mChannel); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIRequest methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetName(nsACString &aName) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->GetName(aName); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::IsPending(bool *aPendingRet) michael@0: { michael@0: *aPendingRet = mIsPending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetStatus(nsresult* aStatusRet) michael@0: { michael@0: *aStatusRet = mStatus; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::Cancel(nsresult status) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->Cancel(status); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::Suspend() michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->Suspend(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::Resume() michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->Resume(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetLoadGroup(nsILoadGroup **aLoadGroup) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->GetLoadGroup(aLoadGroup); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::SetLoadGroup(nsILoadGroup *aLoadGroup) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->SetLoadGroup(aLoadGroup); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetLoadFlags(nsLoadFlags *aLoadFlags) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->GetLoadFlags(aLoadFlags); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::SetLoadFlags(nsLoadFlags aLoadFlags) michael@0: { michael@0: NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); michael@0: return mChannel->SetLoadFlags(aLoadFlags); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIRequestObserver methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::OnStartRequest(nsIRequest *aRequest, nsISupports *aCtxt) michael@0: { michael@0: NS_ASSERTION(aRequest == mChannel, "unexpected request"); michael@0: michael@0: nsresult rv = CheckStatus(); michael@0: if (rv != NS_BASE_STREAM_WOULD_BLOCK) michael@0: SetStatusAndCallBack(rv); michael@0: michael@0: // cancel the request (we don't care to look at the data). michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::OnStopRequest(nsIRequest *request, nsISupports *ctxt, michael@0: nsresult statusCode) michael@0: { michael@0: // NOTE: we may have kicked off a subsequent request, so we should not do michael@0: // any cleanup unless this request matches the one we are currently using. michael@0: if (mChannel == request) { michael@0: // break reference cycle between us and the channel (see comment in michael@0: // AsyncCheckURI) michael@0: mChannel = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIStreamListener methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt, michael@0: nsIInputStream *aInput, uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: NS_NOTREACHED("nsURIChecker::OnDataAvailable"); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIInterfaceRequestor methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: if (mObserver && aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { michael@0: nsCOMPtr req = do_QueryInterface(mObserver); michael@0: if (req) michael@0: return req->GetInterface(aIID, aResult); michael@0: } michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIChannelEventSink methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsURIChecker::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: // We have a new channel michael@0: mChannel = aNewChannel; michael@0: callback->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: }