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