diff options
Diffstat (limited to 'netwerk/protocol/http/nsHttpHandler.cpp')
-rw-r--r-- | netwerk/protocol/http/nsHttpHandler.cpp | 2493 |
1 files changed, 2493 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp new file mode 100644 index 000000000..1ddffabff --- /dev/null +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -0,0 +1,2493 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" + +#include "nsHttp.h" +#include "nsHttpHandler.h" +#include "nsHttpChannel.h" +#include "nsHttpAuthCache.h" +#include "nsStandardURL.h" +#include "nsIDOMWindow.h" +#include "nsIDOMNavigator.h" +#include "nsIMozNavigatorNetwork.h" +#include "nsINetworkProperties.h" +#include "nsIHttpChannel.h" +#include "nsIStandardURL.h" +#include "LoadContextInfo.h" +#include "nsCategoryManagerUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsISocketProviderService.h" +#include "nsISocketProvider.h" +#include "nsPrintfCString.h" +#include "nsCOMPtr.h" +#include "nsNetCID.h" +#include "prprf.h" +#include "mozilla/Sprintf.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsSocketTransportService2.h" +#include "nsAlgorithm.h" +#include "ASpdySession.h" +#include "mozIApplicationClearPrivateDataParams.h" +#include "EventTokenBucket.h" +#include "Tickler.h" +#include "nsIXULAppInfo.h" +#include "nsICookieService.h" +#include "nsIObserverService.h" +#include "nsISiteSecurityService.h" +#include "nsIStreamConverterService.h" +#include "nsITimer.h" +#include "nsCRT.h" +#include "nsIMemoryReporter.h" +#include "nsIParentalControlsService.h" +#include "nsPIDOMWindow.h" +#include "nsINetworkLinkService.h" +#include "nsHttpChannelAuthProvider.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsSocketTransportService2.h" +#include "nsIOService.h" +#include "nsIUUIDGenerator.h" + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/BasePrincipal.h" + +#include "mozilla/dom/ContentParent.h" + +#if defined(XP_UNIX) +#include <sys/utsname.h> +#endif + +#if defined(XP_WIN) +#include <windows.h> +#endif + +#if defined(XP_MACOSX) +#include <CoreServices/CoreServices.h> +#include "nsCocoaFeatures.h" +#endif + +//----------------------------------------------------------------------------- +#include "mozilla/net/HttpChannelChild.h" + + +#define UA_PREF_PREFIX "general.useragent." +#ifdef XP_WIN +#define UA_SPARE_PLATFORM +#endif + +#define HTTP_PREF_PREFIX "network.http." +#define INTL_ACCEPT_LANGUAGES "intl.accept_languages" +#define BROWSER_PREF_PREFIX "browser.cache." +#define DONOTTRACK_HEADER_ENABLED "privacy.donottrackheader.enabled" +#define H2MANDATORY_SUITE "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" +#define TELEMETRY_ENABLED "toolkit.telemetry.enabled" +#define ALLOW_EXPERIMENTS "network.allow-experiments" +#define SAFE_HINT_HEADER_VALUE "safeHint.enabled" +#define SECURITY_PREFIX "security." +#define NEW_TAB_REMOTE_MODE "browser.newtabpage.remote.mode" + +#define UA_PREF(_pref) UA_PREF_PREFIX _pref +#define HTTP_PREF(_pref) HTTP_PREF_PREFIX _pref +#define BROWSER_PREF(_pref) BROWSER_PREF_PREFIX _pref + +#define NS_HTTP_PROTOCOL_FLAGS (URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE) + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +LazyLogModule gHttpLog("nsHttp"); + +static nsresult +NewURI(const nsACString &aSpec, + const char *aCharset, + nsIURI *aBaseURI, + int32_t aDefaultPort, + nsIURI **aURI) +{ + RefPtr<nsStandardURL> url = new nsStandardURL(); + + nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, + aDefaultPort, aSpec, aCharset, aBaseURI); + if (NS_FAILED(rv)) { + return rv; + } + + url.forget(aURI); + return NS_OK; +} + +#ifdef ANDROID +static nsCString +GetDeviceModelId() { + // Assumed to be running on the main thread + // We need the device property in either case + nsAutoCString deviceModelId; + nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1"); + MOZ_ASSERT(infoService, "Could not find a system info service"); + nsAutoString androidDevice; + nsresult rv = infoService->GetPropertyAsAString(NS_LITERAL_STRING("device"), androidDevice); + if (NS_SUCCEEDED(rv)) { + deviceModelId = NS_LossyConvertUTF16toASCII(androidDevice); + } + nsAutoCString deviceString; + rv = Preferences::GetCString(UA_PREF("device_string"), &deviceString); + if (NS_SUCCEEDED(rv)) { + deviceString.Trim(" ", true, true); + deviceString.ReplaceSubstring(NS_LITERAL_CSTRING("%DEVICEID%"), deviceModelId); + return deviceString; + } + return deviceModelId; +} +#endif + +//----------------------------------------------------------------------------- +// nsHttpHandler <public> +//----------------------------------------------------------------------------- + +nsHttpHandler *gHttpHandler = nullptr; + +nsHttpHandler::nsHttpHandler() + : mHttpVersion(NS_HTTP_VERSION_1_1) + , mProxyHttpVersion(NS_HTTP_VERSION_1_1) + , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE) + , mReferrerLevel(0xff) // by default we always send a referrer + , mSpoofReferrerSource(false) + , mReferrerTrimmingPolicy(0) + , mReferrerXOriginTrimmingPolicy(0) + , mReferrerXOriginPolicy(0) + , mFastFallbackToIPv4(false) + , mProxyPipelining(true) + , mIdleTimeout(PR_SecondsToInterval(10)) + , mSpdyTimeout(PR_SecondsToInterval(180)) + , mResponseTimeout(PR_SecondsToInterval(300)) + , mResponseTimeoutEnabled(false) + , mNetworkChangedTimeout(5000) + , mMaxRequestAttempts(6) + , mMaxRequestDelay(10) + , mIdleSynTimeout(250) + , mH2MandatorySuiteEnabled(false) + , mPipeliningEnabled(false) + , mMaxConnections(24) + , mMaxPersistentConnectionsPerServer(2) + , mMaxPersistentConnectionsPerProxy(4) + , mMaxPipelinedRequests(32) + , mMaxOptimisticPipelinedRequests(4) + , mPipelineAggressive(false) + , mMaxPipelineObjectSize(300000) + , mPipelineRescheduleOnTimeout(true) + , mPipelineRescheduleTimeout(PR_MillisecondsToInterval(1500)) + , mPipelineReadTimeout(PR_MillisecondsToInterval(30000)) + , mRedirectionLimit(10) + , mPhishyUserPassLength(1) + , mQoSBits(0x00) + , mPipeliningOverSSL(false) + , mEnforceAssocReq(false) + , mLastUniqueID(NowInSeconds()) + , mSessionStartTime(0) + , mLegacyAppName("Mozilla") + , mLegacyAppVersion("5.0") + , mProduct("Gecko") + , mCompatFirefoxEnabled(false) + , mUserAgentIsDirty(true) + , mPromptTempRedirect(true) + , mEnablePersistentHttpsCaching(false) + , mDoNotTrackEnabled(false) + , mSafeHintEnabled(false) + , mParentalControlEnabled(false) + , mHandlerActive(false) + , mTelemetryEnabled(false) + , mAllowExperiments(true) + , mDebugObservations(false) + , mEnableSpdy(false) + , mHttp2Enabled(true) + , mUseH2Deps(true) + , mEnforceHttp2TlsProfile(true) + , mCoalesceSpdy(true) + , mSpdyPersistentSettings(false) + , mAllowPush(true) + , mEnableAltSvc(false) + , mEnableAltSvcOE(false) + , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize) + , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize) + , mSpdyPushAllowance(32768) + , mSpdyPullAllowance(ASpdySession::kInitialRwin) + , mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent) + , mSpdyPingThreshold(PR_SecondsToInterval(58)) + , mSpdyPingTimeout(PR_SecondsToInterval(8)) + , mConnectTimeout(90000) + , mParallelSpeculativeConnectLimit(6) + , mRequestTokenBucketEnabled(true) + , mRequestTokenBucketMinParallelism(6) + , mRequestTokenBucketHz(100) + , mRequestTokenBucketBurst(32) + , mCriticalRequestPrioritization(true) + , mTCPKeepaliveShortLivedEnabled(false) + , mTCPKeepaliveShortLivedTimeS(60) + , mTCPKeepaliveShortLivedIdleTimeS(10) + , mTCPKeepaliveLongLivedEnabled(false) + , mTCPKeepaliveLongLivedIdleTimeS(600) + , mEnforceH1Framing(FRAMECHECK_BARELY) + , mKeepEmptyResponseHeadersAsEmtpyString(false) + , mDefaultHpackBuffer(4096) + , mMaxHttpResponseHeaderSize(393216) +{ + LOG(("Creating nsHttpHandler [this=%p].\n", this)); + + MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!"); + gHttpHandler = this; +} + +nsHttpHandler::~nsHttpHandler() +{ + LOG(("Deleting nsHttpHandler [this=%p]\n", this)); + + // make sure the connection manager is shutdown + if (mConnMgr) { + mConnMgr->Shutdown(); + mConnMgr = nullptr; + } + + // Note: don't call NeckoChild::DestroyNeckoChild() here, as it's too late + // and it'll segfault. NeckoChild will get cleaned up by process exit. + + nsHttp::DestroyAtomTable(); + if (mPipelineTestTimer) { + mPipelineTestTimer->Cancel(); + mPipelineTestTimer = nullptr; + } + + gHttpHandler = nullptr; +} + +nsresult +nsHttpHandler::Init() +{ + nsresult rv; + + LOG(("nsHttpHandler::Init\n")); + MOZ_ASSERT(NS_IsMainThread()); + + rv = nsHttp::CreateAtomTable(); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIIOService> service = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to continue without io service"); + return rv; + } + mIOService = new nsMainThreadPtrHolder<nsIIOService>(service); + + if (IsNeckoChild()) + NeckoChild::InitNeckoChild(); + + InitUserAgentComponents(); + + // monitor some preference changes + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefBranch) { + prefBranch->AddObserver(HTTP_PREF_PREFIX, this, true); + prefBranch->AddObserver(UA_PREF_PREFIX, this, true); + prefBranch->AddObserver(INTL_ACCEPT_LANGUAGES, this, true); + prefBranch->AddObserver(BROWSER_PREF("disk_cache_ssl"), this, true); + prefBranch->AddObserver(DONOTTRACK_HEADER_ENABLED, this, true); + prefBranch->AddObserver(TELEMETRY_ENABLED, this, true); + prefBranch->AddObserver(H2MANDATORY_SUITE, this, true); + prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.short_lived_connections"), this, true); + prefBranch->AddObserver(HTTP_PREF("tcp_keepalive.long_lived_connections"), this, true); + prefBranch->AddObserver(SAFE_HINT_HEADER_VALUE, this, true); + prefBranch->AddObserver(SECURITY_PREFIX, this, true); + prefBranch->AddObserver(NEW_TAB_REMOTE_MODE, this, true); + PrefsChanged(prefBranch, nullptr); + } + + nsHttpChannelAuthProvider::InitializePrefs(); + + mMisc.AssignLiteral("rv:" MOZILLA_UAVERSION); + + mCompatFirefox.AssignLiteral("Firefox/" MOZILLA_UAVERSION); + + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + + mAppName.AssignLiteral(MOZ_APP_UA_NAME); + if (mAppName.Length() == 0 && appInfo) { + // Try to get the UA name from appInfo, falling back to the name + appInfo->GetUAName(mAppName); + if (mAppName.Length() == 0) { + appInfo->GetName(mAppName); + } + appInfo->GetVersion(mAppVersion); + mAppName.StripChars(R"( ()<>@,;:\"/[]?={})"); + } else { + mAppVersion.AssignLiteral(MOZ_APP_UA_VERSION); + } + + mSessionStartTime = NowInSeconds(); + mHandlerActive = true; + + rv = mAuthCache.Init(); + if (NS_FAILED(rv)) return rv; + + rv = mPrivateAuthCache.Init(); + if (NS_FAILED(rv)) return rv; + + rv = InitConnectionMgr(); + if (NS_FAILED(rv)) return rv; + + mRequestContextService = + do_GetService("@mozilla.org/network/request-context-service;1"); + +#if defined(ANDROID) || defined(MOZ_MULET) + mProductSub.AssignLiteral(MOZILLA_UAVERSION); +#else + mProductSub.AssignLiteral("20100101"); +#endif + +#if DEBUG + // dump user agent prefs + LOG(("> legacy-app-name = %s\n", mLegacyAppName.get())); + LOG(("> legacy-app-version = %s\n", mLegacyAppVersion.get())); + LOG(("> platform = %s\n", mPlatform.get())); + LOG(("> oscpu = %s\n", mOscpu.get())); + LOG(("> misc = %s\n", mMisc.get())); + LOG(("> product = %s\n", mProduct.get())); + LOG(("> product-sub = %s\n", mProductSub.get())); + LOG(("> app-name = %s\n", mAppName.get())); + LOG(("> app-version = %s\n", mAppVersion.get())); + LOG(("> compat-firefox = %s\n", mCompatFirefox.get())); + LOG(("> user-agent = %s\n", UserAgent().get())); +#endif + + // Startup the http category + // Bring alive the objects in the http-protocol-startup category + NS_CreateServicesFromCategory(NS_HTTP_STARTUP_CATEGORY, + static_cast<nsISupports*>(static_cast<void*>(this)), + NS_HTTP_STARTUP_TOPIC); + + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + if (obsService) { + // register the handler object as a weak callback as we don't need to worry + // about shutdown ordering. + obsService->AddObserver(this, "profile-change-net-teardown", true); + obsService->AddObserver(this, "profile-change-net-restore", true); + obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + obsService->AddObserver(this, "net:clear-active-logins", true); + obsService->AddObserver(this, "net:prune-dead-connections", true); + // Sent by the TorButton add-on in the Tor Browser + obsService->AddObserver(this, "net:prune-all-connections", true); + obsService->AddObserver(this, "net:failed-to-process-uri-content", true); + obsService->AddObserver(this, "last-pb-context-exited", true); + obsService->AddObserver(this, "webapps-clear-data", true); + obsService->AddObserver(this, "browser:purge-session-history", true); + obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true); + obsService->AddObserver(this, "application-background", true); + } + + MakeNewRequestTokenBucket(); + mWifiTickler = new Tickler(); + if (NS_FAILED(mWifiTickler->Init())) + mWifiTickler = nullptr; + + nsCOMPtr<nsIParentalControlsService> pc = do_CreateInstance("@mozilla.org/parental-controls-service;1"); + if (pc) { + pc->GetParentalControlsEnabled(&mParentalControlEnabled); + } + return NS_OK; +} + +void +nsHttpHandler::MakeNewRequestTokenBucket() +{ + LOG(("nsHttpHandler::MakeNewRequestTokenBucket this=%p child=%d\n", + this, IsNeckoChild())); + if (!mConnMgr || IsNeckoChild()) { + return; + } + RefPtr<EventTokenBucket> tokenBucket = + new EventTokenBucket(RequestTokenBucketHz(), RequestTokenBucketBurst()); + mConnMgr->UpdateRequestTokenBucket(tokenBucket); +} + +nsresult +nsHttpHandler::InitConnectionMgr() +{ + // Init ConnectionManager only on parent! + if (IsNeckoChild()) { + return NS_OK; + } + + nsresult rv; + + if (!mConnMgr) { + mConnMgr = new nsHttpConnectionMgr(); + } + + rv = mConnMgr->Init(mMaxConnections, + mMaxPersistentConnectionsPerServer, + mMaxPersistentConnectionsPerProxy, + mMaxRequestDelay, + mMaxPipelinedRequests, + mMaxOptimisticPipelinedRequests); + return rv; +} + +nsresult +nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure) +{ + nsresult rv; + + // Add the "User-Agent" header + rv = request->SetHeader(nsHttp::User_Agent, UserAgent(), + false, nsHttpHeaderArray::eVarietyRequestDefault); + if (NS_FAILED(rv)) return rv; + + // MIME based content negotiation lives! + // Add the "Accept" header. Note, this is set as an override because the + // service worker expects to see it. The other "default" headers are + // hidden from service worker interception. + rv = request->SetHeader(nsHttp::Accept, mAccept, + false, nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) return rv; + + // Add the "Accept-Language" header. This header is also exposed to the + // service worker. + if (!mAcceptLanguages.IsEmpty()) { + // Add the "Accept-Language" header + rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, + false, + nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) return rv; + } + + // Add the "Accept-Encoding" header + if (isSecure) { + rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings, + false, + nsHttpHeaderArray::eVarietyRequestDefault); + } else { + rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings, + false, + nsHttpHeaderArray::eVarietyRequestDefault); + } + if (NS_FAILED(rv)) return rv; + + // add the "Send Hint" header + if (mSafeHintEnabled || mParentalControlEnabled) { + rv = request->SetHeader(nsHttp::Prefer, NS_LITERAL_CSTRING("safe"), + false, + nsHttpHeaderArray::eVarietyRequestDefault); + if (NS_FAILED(rv)) return rv; + } + return NS_OK; +} + +nsresult +nsHttpHandler::AddConnectionHeader(nsHttpRequestHead *request, + uint32_t caps) +{ + // RFC2616 section 19.6.2 states that the "Connection: keep-alive" + // and "Keep-alive" request headers should not be sent by HTTP/1.1 + // user-agents. But this is not a problem in practice, and the + // alternative proxy-connection is worse. see 570283 + + NS_NAMED_LITERAL_CSTRING(close, "close"); + NS_NAMED_LITERAL_CSTRING(keepAlive, "keep-alive"); + + const nsACString *connectionType = &close; + if (caps & NS_HTTP_ALLOW_KEEPALIVE) { + connectionType = &keepAlive; + } + + return request->SetHeader(nsHttp::Connection, *connectionType); +} + +bool +nsHttpHandler::IsAcceptableEncoding(const char *enc, bool isSecure) +{ + if (!enc) + return false; + + // we used to accept x-foo anytime foo was acceptable, but that's just + // continuing bad behavior.. so limit it to known x-* patterns + bool rv; + if (isSecure) { + rv = nsHttp::FindToken(mHttpsAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr; + } else { + rv = nsHttp::FindToken(mHttpAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr; + } + // gzip and deflate are inherently acceptable in modern HTTP - always + // process them if a stream converter can also be found. + if (!rv && + (!PL_strcasecmp(enc, "gzip") || !PL_strcasecmp(enc, "deflate") || + !PL_strcasecmp(enc, "x-gzip") || !PL_strcasecmp(enc, "x-deflate"))) { + rv = true; + } + LOG(("nsHttpHandler::IsAceptableEncoding %s https=%d %d\n", + enc, isSecure, rv)); + return rv; +} + +nsresult +nsHttpHandler::GetStreamConverterService(nsIStreamConverterService **result) +{ + if (!mStreamConvSvc) { + nsresult rv; + nsCOMPtr<nsIStreamConverterService> service = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + mStreamConvSvc = new nsMainThreadPtrHolder<nsIStreamConverterService>(service); + } + *result = mStreamConvSvc; + NS_ADDREF(*result); + return NS_OK; +} + +nsISiteSecurityService* +nsHttpHandler::GetSSService() +{ + if (!mSSService) { + nsCOMPtr<nsISiteSecurityService> service = do_GetService(NS_SSSERVICE_CONTRACTID); + mSSService = new nsMainThreadPtrHolder<nsISiteSecurityService>(service); + } + return mSSService; +} + +nsICookieService * +nsHttpHandler::GetCookieService() +{ + if (!mCookieService) { + nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID); + mCookieService = new nsMainThreadPtrHolder<nsICookieService>(service); + } + return mCookieService; +} + +nsresult +nsHttpHandler::GetIOService(nsIIOService** result) +{ + NS_ENSURE_ARG_POINTER(result); + + NS_ADDREF(*result = mIOService); + return NS_OK; +} + +uint32_t +nsHttpHandler::Get32BitsOfPseudoRandom() +{ + // only confirm rand seeding on socket thread + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + // rand() provides different amounts of PRNG on different platforms. + // 15 or 31 bits are common amounts. + + static_assert(RAND_MAX >= 0xfff, "RAND_MAX should be >= 12 bits"); + +#if RAND_MAX < 0xffffU + return ((uint16_t) rand() << 20) | + (((uint16_t) rand() & 0xfff) << 8) | + ((uint16_t) rand() & 0xff); +#elif RAND_MAX < 0xffffffffU + return ((uint16_t) rand() << 16) | ((uint16_t) rand() & 0xffff); +#else + return (uint32_t) rand(); +#endif +} + +void +nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event) +{ + LOG(("nsHttpHandler::NotifyObservers [chan=%x event=\"%s\"]\n", chan, event)); + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(chan, event, nullptr); +} + +nsresult +nsHttpHandler::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan, + uint32_t flags) +{ + // TODO E10S This helper has to be initialized on the other process + RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper = + new nsAsyncRedirectVerifyHelper(); + + return redirectCallbackHelper->Init(oldChan, newChan, flags); +} + +/* static */ nsresult +nsHttpHandler::GenerateHostPort(const nsCString& host, int32_t port, + nsACString& hostLine) +{ + return NS_GenerateHostPort(host, port, hostLine); +} + +//----------------------------------------------------------------------------- +// nsHttpHandler <private> +//----------------------------------------------------------------------------- + +const nsAFlatCString & +nsHttpHandler::UserAgent() +{ + if (mUserAgentOverride) { + LOG(("using general.useragent.override : %s\n", mUserAgentOverride.get())); + return mUserAgentOverride; + } + + if (mUserAgentIsDirty) { + BuildUserAgent(); + mUserAgentIsDirty = false; + } + + return mUserAgent; +} + +void +nsHttpHandler::BuildUserAgent() +{ + LOG(("nsHttpHandler::BuildUserAgent\n")); + + MOZ_ASSERT(!mLegacyAppName.IsEmpty() && + !mLegacyAppVersion.IsEmpty(), + "HTTP cannot send practical requests without this much"); + + // preallocate to worst-case size, which should always be better + // than if we didn't preallocate at all. + mUserAgent.SetCapacity(mLegacyAppName.Length() + + mLegacyAppVersion.Length() + +#ifndef UA_SPARE_PLATFORM + mPlatform.Length() + +#endif + mOscpu.Length() + + mMisc.Length() + + mProduct.Length() + + mProductSub.Length() + + mAppName.Length() + + mAppVersion.Length() + + mCompatFirefox.Length() + + mCompatDevice.Length() + + mDeviceModelId.Length() + + 13); + + // Application portion + mUserAgent.Assign(mLegacyAppName); + mUserAgent += '/'; + mUserAgent += mLegacyAppVersion; + mUserAgent += ' '; + + // Application comment + mUserAgent += '('; +#ifndef UA_SPARE_PLATFORM + if (!mPlatform.IsEmpty()) { + mUserAgent += mPlatform; + mUserAgent.AppendLiteral("; "); + } +#endif + if (!mCompatDevice.IsEmpty()) { + mUserAgent += mCompatDevice; + mUserAgent.AppendLiteral("; "); + } + else if (!mOscpu.IsEmpty()) { + mUserAgent += mOscpu; + mUserAgent.AppendLiteral("; "); + } + if (!mDeviceModelId.IsEmpty()) { + mUserAgent += mDeviceModelId; + mUserAgent.AppendLiteral("; "); + } + mUserAgent += mMisc; + mUserAgent += ')'; + + // Product portion + mUserAgent += ' '; + mUserAgent += mProduct; + mUserAgent += '/'; + mUserAgent += mProductSub; + + bool isFirefox = mAppName.EqualsLiteral("Firefox"); + if (isFirefox || mCompatFirefoxEnabled) { + // "Firefox/x.y" (compatibility) app token + mUserAgent += ' '; + mUserAgent += mCompatFirefox; + } + if (!isFirefox) { + // App portion + mUserAgent += ' '; + mUserAgent += mAppName; + mUserAgent += '/'; + mUserAgent += mAppVersion; + } +} + +#ifdef XP_WIN +#define WNT_BASE "Windows NT %ld.%ld" +#define W64_PREFIX "; Win64" +#endif + +void +nsHttpHandler::InitUserAgentComponents() +{ +#ifndef MOZ_UA_OS_AGNOSTIC + // Gather platform. + mPlatform.AssignLiteral( +#if defined(ANDROID) + "Android" +#elif defined(XP_WIN) + "Windows" +#elif defined(XP_MACOSX) + "Macintosh" +#elif defined(XP_UNIX) + // We historically have always had X11 here, + // and there seems little a webpage can sensibly do + // based on it being something else, so use X11 for + // backwards compatibility in all cases. + "X11" +#endif + ); +#endif + + +#ifdef ANDROID + nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1"); + MOZ_ASSERT(infoService, "Could not find a system info service"); + nsresult rv; + // Add the Android version number to the Fennec platform identifier. +#if defined MOZ_WIDGET_ANDROID +#ifndef MOZ_UA_OS_AGNOSTIC // Don't add anything to mPlatform since it's empty. + nsAutoString androidVersion; + rv = infoService->GetPropertyAsAString( + NS_LITERAL_STRING("release_version"), androidVersion); + if (NS_SUCCEEDED(rv)) { + mPlatform += " "; + // If the 2nd character is a ".", we know the major version is a single + // digit. If we're running on a version below 4 we pretend to be on + // Android KitKat (4.4) to work around scripts sniffing for low versions. + if (androidVersion[1] == 46 && androidVersion[0] < 52) { + mPlatform += "4.4"; + } else { + mPlatform += NS_LossyConvertUTF16toASCII(androidVersion); + } + } +#endif +#endif + // Add the `Mobile` or `Tablet` or `TV` token when running on device. + bool isTablet; + rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tablet"), &isTablet); + if (NS_SUCCEEDED(rv) && isTablet) { + mCompatDevice.AssignLiteral("Tablet"); + } else { + bool isTV; + rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tv"), &isTV); + if (NS_SUCCEEDED(rv) && isTV) { + mCompatDevice.AssignLiteral("TV"); + } else { + mCompatDevice.AssignLiteral("Mobile"); + } + } + + if (Preferences::GetBool(UA_PREF("use_device"), false)) { + mDeviceModelId = mozilla::net::GetDeviceModelId(); + } +#endif // ANDROID + +#ifdef MOZ_MULET + { + // Add the `Mobile` or `Tablet` or `TV` token when running in the b2g + // desktop simulator via preference. + nsCString deviceType; + nsresult rv = Preferences::GetCString("devtools.useragent.device_type", &deviceType); + if (NS_SUCCEEDED(rv)) { + mCompatDevice.Assign(deviceType); + } else { + mCompatDevice.AssignLiteral("Mobile"); + } + } +#endif // MOZ_MULET + +#if defined(MOZ_WIDGET_GONK) + // Device model identifier should be a simple token, which can be composed + // of letters, numbers, hyphen ("-") and dot ("."). + // Any other characters means the identifier is invalid and ignored. + nsCString deviceId; + rv = Preferences::GetCString("general.useragent.device_id", &deviceId); + if (NS_SUCCEEDED(rv)) { + bool valid = true; + deviceId.Trim(" ", true, true); + for (size_t i = 0; i < deviceId.Length(); i++) { + char c = deviceId.CharAt(i); + if (!(isalnum(c) || c == '-' || c == '.')) { + valid = false; + break; + } + } + if (valid) { + mDeviceModelId = deviceId; + } else { + LOG(("nsHttpHandler: Ignore invalid device ID: [%s]\n", + deviceId.get())); + } + } +#endif + +#ifndef MOZ_UA_OS_AGNOSTIC + // Gather OS/CPU. +#if defined(XP_WIN) + OSVERSIONINFO info = { sizeof(OSVERSIONINFO) }; +#pragma warning(push) +#pragma warning(disable:4996) + if (GetVersionEx(&info)) { +#pragma warning(pop) + const char *format; +#if defined _M_IA64 + format = WNT_BASE W64_PREFIX "; IA64"; +#elif defined _M_X64 || defined _M_AMD64 + format = WNT_BASE W64_PREFIX "; x64"; +#else + BOOL isWow64 = FALSE; + if (!IsWow64Process(GetCurrentProcess(), &isWow64)) { + isWow64 = FALSE; + } + format = isWow64 + ? WNT_BASE "; WOW64" + : WNT_BASE; +#endif + char *buf = PR_smprintf(format, + info.dwMajorVersion, + info.dwMinorVersion); + if (buf) { + mOscpu = buf; + PR_smprintf_free(buf); + } + } +#elif defined (XP_MACOSX) +#if defined(__ppc__) + mOscpu.AssignLiteral("PPC Mac OS X"); +#elif defined(__i386__) || defined(__x86_64__) + mOscpu.AssignLiteral("Intel Mac OS X"); +#endif + SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor(); + SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor(); + mOscpu += nsPrintfCString(" %d.%d", majorVersion, minorVersion); +#elif defined (XP_UNIX) + struct utsname name; + + int ret = uname(&name); + if (ret >= 0) { + nsAutoCString buf; + buf = (char*)name.sysname; + + if (strcmp(name.machine, "x86_64") == 0 && + sizeof(void *) == sizeof(int32_t)) { + // We're running 32-bit code on x86_64. Make this browser + // look like it's running on i686 hardware, but append " + // (x86_64)" to the end of the oscpu identifier to be able + // to differentiate this from someone running 64-bit code + // on x86_64.. + + buf += " i686 on x86_64"; + } else { + buf += ' '; + +#ifdef AIX + // AIX uname returns machine specific info in the uname.machine + // field and does not return the cpu type like other platforms. + // We use the AIX version and release numbers instead. + buf += (char*)name.version; + buf += '.'; + buf += (char*)name.release; +#else + buf += (char*)name.machine; +#endif + } + + mOscpu.Assign(buf); + } +#endif +#endif + + mUserAgentIsDirty = true; +} + +uint32_t +nsHttpHandler::MaxSocketCount() +{ + PR_CallOnce(&nsSocketTransportService::gMaxCountInitOnce, + nsSocketTransportService::DiscoverMaxCount); + // Don't use the full max count because sockets can be held in + // the persistent connection pool for a long time and that could + // starve other users. + + uint32_t maxCount = nsSocketTransportService::gMaxCount; + if (maxCount <= 8) + maxCount = 1; + else + maxCount -= 8; + + return maxCount; +} + +void +nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) +{ + nsresult rv = NS_OK; + int32_t val; + + LOG(("nsHttpHandler::PrefsChanged [pref=%s]\n", pref)); + +#define PREF_CHANGED(p) ((pref == nullptr) || !PL_strcmp(pref, p)) +#define MULTI_PREF_CHANGED(p) \ + ((pref == nullptr) || !PL_strncmp(pref, p, sizeof(p) - 1)) + + // If a security pref changed, lets clear our connection pool reuse + if (MULTI_PREF_CHANGED(SECURITY_PREFIX)) { + LOG(("nsHttpHandler::PrefsChanged Security Pref Changed %s\n", pref)); + if (mConnMgr) { + mConnMgr->DoShiftReloadConnectionCleanup(nullptr); + mConnMgr->PruneDeadConnections(); + } + } + + // + // UA components + // + + bool cVar = false; + + if (PREF_CHANGED(UA_PREF("compatMode.firefox"))) { + rv = prefs->GetBoolPref(UA_PREF("compatMode.firefox"), &cVar); + mCompatFirefoxEnabled = (NS_SUCCEEDED(rv) && cVar); + mUserAgentIsDirty = true; + } + + // general.useragent.override + if (PREF_CHANGED(UA_PREF("override"))) { + prefs->GetCharPref(UA_PREF("override"), + getter_Copies(mUserAgentOverride)); + mUserAgentIsDirty = true; + } + +#ifdef ANDROID + // general.useragent.use_device + if (PREF_CHANGED(UA_PREF("use_device"))) { + if (Preferences::GetBool(UA_PREF("use_device"), false)) { + mDeviceModelId = mozilla::net::GetDeviceModelId(); + } else { + mDeviceModelId = EmptyCString(); + } + mUserAgentIsDirty = true; + } +#endif + + // + // HTTP options + // + + if (PREF_CHANGED(HTTP_PREF("keep-alive.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("keep-alive.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mIdleTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff)); + } + + if (PREF_CHANGED(HTTP_PREF("request.max-attempts"))) { + rv = prefs->GetIntPref(HTTP_PREF("request.max-attempts"), &val); + if (NS_SUCCEEDED(rv)) + mMaxRequestAttempts = (uint16_t) clamped(val, 1, 0xffff); + } + + if (PREF_CHANGED(HTTP_PREF("request.max-start-delay"))) { + rv = prefs->GetIntPref(HTTP_PREF("request.max-start-delay"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxRequestDelay = (uint16_t) clamped(val, 0, 0xffff); + if (mConnMgr) + mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_REQUEST_DELAY, + mMaxRequestDelay); + } + } + + if (PREF_CHANGED(HTTP_PREF("response.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("response.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mResponseTimeout = PR_SecondsToInterval(clamped(val, 0, 0xffff)); + } + + if (PREF_CHANGED(HTTP_PREF("network-changed.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("network-changed.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mNetworkChangedTimeout = clamped(val, 1, 600) * 1000; + } + + if (PREF_CHANGED(HTTP_PREF("max-connections"))) { + rv = prefs->GetIntPref(HTTP_PREF("max-connections"), &val); + if (NS_SUCCEEDED(rv)) { + + mMaxConnections = (uint16_t) clamped((uint32_t)val, + (uint32_t)1, MaxSocketCount()); + + if (mConnMgr) + mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_CONNECTIONS, + mMaxConnections); + } + } + + if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-server"))) { + rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-server"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxPersistentConnectionsPerServer = (uint8_t) clamped(val, 1, 0xff); + if (mConnMgr) + mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_HOST, + mMaxPersistentConnectionsPerServer); + } + } + + if (PREF_CHANGED(HTTP_PREF("max-persistent-connections-per-proxy"))) { + rv = prefs->GetIntPref(HTTP_PREF("max-persistent-connections-per-proxy"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxPersistentConnectionsPerProxy = (uint8_t) clamped(val, 1, 0xff); + if (mConnMgr) + mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PERSISTENT_CONNECTIONS_PER_PROXY, + mMaxPersistentConnectionsPerProxy); + } + } + + if (PREF_CHANGED(HTTP_PREF("sendRefererHeader"))) { + rv = prefs->GetIntPref(HTTP_PREF("sendRefererHeader"), &val); + if (NS_SUCCEEDED(rv)) + mReferrerLevel = (uint8_t) clamped(val, 0, 0xff); + } + + if (PREF_CHANGED(HTTP_PREF("referer.spoofSource"))) { + rv = prefs->GetBoolPref(HTTP_PREF("referer.spoofSource"), &cVar); + if (NS_SUCCEEDED(rv)) + mSpoofReferrerSource = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("referer.trimmingPolicy"))) { + rv = prefs->GetIntPref(HTTP_PREF("referer.trimmingPolicy"), &val); + if (NS_SUCCEEDED(rv)) + mReferrerTrimmingPolicy = (uint8_t) clamped(val, 0, 2); + } + + if (PREF_CHANGED(HTTP_PREF("referer.XOriginTrimmingPolicy"))) { + rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginTrimmingPolicy"), &val); + if (NS_SUCCEEDED(rv)) + mReferrerXOriginTrimmingPolicy = (uint8_t) clamped(val, 0, 2); + } + + if (PREF_CHANGED(HTTP_PREF("referer.XOriginPolicy"))) { + rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginPolicy"), &val); + if (NS_SUCCEEDED(rv)) + mReferrerXOriginPolicy = (uint8_t) clamped(val, 0, 0xff); + } + + if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) { + rv = prefs->GetIntPref(HTTP_PREF("redirection-limit"), &val); + if (NS_SUCCEEDED(rv)) + mRedirectionLimit = (uint8_t) clamped(val, 0, 0xff); + } + + if (PREF_CHANGED(HTTP_PREF("connection-retry-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("connection-retry-timeout"), &val); + if (NS_SUCCEEDED(rv)) + mIdleSynTimeout = (uint16_t) clamped(val, 0, 3000); + } + + if (PREF_CHANGED(HTTP_PREF("fast-fallback-to-IPv4"))) { + rv = prefs->GetBoolPref(HTTP_PREF("fast-fallback-to-IPv4"), &cVar); + if (NS_SUCCEEDED(rv)) + mFastFallbackToIPv4 = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("version"))) { + nsXPIDLCString httpVersion; + prefs->GetCharPref(HTTP_PREF("version"), getter_Copies(httpVersion)); + if (httpVersion) { + if (!PL_strcmp(httpVersion, "1.1")) + mHttpVersion = NS_HTTP_VERSION_1_1; + else if (!PL_strcmp(httpVersion, "0.9")) + mHttpVersion = NS_HTTP_VERSION_0_9; + else + mHttpVersion = NS_HTTP_VERSION_1_0; + } + } + + if (PREF_CHANGED(HTTP_PREF("proxy.version"))) { + nsXPIDLCString httpVersion; + prefs->GetCharPref(HTTP_PREF("proxy.version"), getter_Copies(httpVersion)); + if (httpVersion) { + if (!PL_strcmp(httpVersion, "1.1")) + mProxyHttpVersion = NS_HTTP_VERSION_1_1; + else + mProxyHttpVersion = NS_HTTP_VERSION_1_0; + // it does not make sense to issue a HTTP/0.9 request to a proxy server + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining"), &cVar); + if (NS_SUCCEEDED(rv)) { + if (cVar) + mCapabilities |= NS_HTTP_ALLOW_PIPELINING; + else + mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING; + mPipeliningEnabled = cVar; + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxPipelinedRequests = clamped(val, 1, 0xffff); + if (mConnMgr) + mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS, + mMaxPipelinedRequests); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) { + rv = prefs-> + GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff); + if (mConnMgr) + mConnMgr->UpdateParam + (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS, + mMaxOptimisticPipelinedRequests); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar); + if (NS_SUCCEEDED(rv)) + mPipelineAggressive = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.maxsize"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxPipelineObjectSize = + static_cast<int64_t>(clamped(val, 1000, 100000000)); + } + } + + // Determines whether or not to actually reschedule after the + // reschedule-timeout has expired + if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-on-timeout"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.reschedule-on-timeout"), + &cVar); + if (NS_SUCCEEDED(rv)) + mPipelineRescheduleOnTimeout = cVar; + } + + // The amount of time head of line blocking is allowed (in ms) + // before the blocked transactions are moved to another pipeline + if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.reschedule-timeout"), + &val); + if (NS_SUCCEEDED(rv)) { + mPipelineRescheduleTimeout = + PR_MillisecondsToInterval((uint16_t) clamped(val, 500, 0xffff)); + } + } + + // The amount of time a pipelined transaction is allowed to wait before + // being canceled and retried in a non-pipeline connection + if (PREF_CHANGED(HTTP_PREF("pipelining.read-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.read-timeout"), &val); + if (NS_SUCCEEDED(rv)) { + mPipelineReadTimeout = + PR_MillisecondsToInterval((uint16_t) clamped(val, 5000, + 0xffff)); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar); + if (NS_SUCCEEDED(rv)) + mPipeliningOverSSL = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) { + rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar); + if (NS_SUCCEEDED(rv)) + mProxyPipelining = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("qos"))) { + rv = prefs->GetIntPref(HTTP_PREF("qos"), &val); + if (NS_SUCCEEDED(rv)) + mQoSBits = (uint8_t) clamped(val, 0, 0xff); + } + + if (PREF_CHANGED(HTTP_PREF("accept.default"))) { + nsXPIDLCString accept; + rv = prefs->GetCharPref(HTTP_PREF("accept.default"), + getter_Copies(accept)); + if (NS_SUCCEEDED(rv)) + SetAccept(accept); + } + + if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) { + nsXPIDLCString acceptEncodings; + rv = prefs->GetCharPref(HTTP_PREF("accept-encoding"), + getter_Copies(acceptEncodings)); + if (NS_SUCCEEDED(rv)) { + SetAcceptEncodings(acceptEncodings, false); + } + } + + if (PREF_CHANGED(HTTP_PREF("accept-encoding.secure"))) { + nsXPIDLCString acceptEncodings; + rv = prefs->GetCharPref(HTTP_PREF("accept-encoding.secure"), + getter_Copies(acceptEncodings)); + if (NS_SUCCEEDED(rv)) { + SetAcceptEncodings(acceptEncodings, true); + } + } + + if (PREF_CHANGED(HTTP_PREF("default-socket-type"))) { + nsXPIDLCString sval; + rv = prefs->GetCharPref(HTTP_PREF("default-socket-type"), + getter_Copies(sval)); + if (NS_SUCCEEDED(rv)) { + if (sval.IsEmpty()) + mDefaultSocketType.Adopt(0); + else { + // verify that this socket type is actually valid + nsCOMPtr<nsISocketProviderService> sps( + do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID)); + if (sps) { + nsCOMPtr<nsISocketProvider> sp; + rv = sps->GetSocketProvider(sval, getter_AddRefs(sp)); + if (NS_SUCCEEDED(rv)) { + // OK, this looks like a valid socket provider. + mDefaultSocketType.Assign(sval); + } + } + } + } + } + + if (PREF_CHANGED(HTTP_PREF("prompt-temp-redirect"))) { + rv = prefs->GetBoolPref(HTTP_PREF("prompt-temp-redirect"), &cVar); + if (NS_SUCCEEDED(rv)) { + mPromptTempRedirect = cVar; + } + } + + if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) { + cVar = false; + rv = prefs->GetBoolPref(HTTP_PREF("assoc-req.enforce"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnforceAssocReq = cVar; + } + + // enable Persistent caching for HTTPS - bug#205921 + if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) { + cVar = false; + rv = prefs->GetBoolPref(BROWSER_PREF("disk_cache_ssl"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnablePersistentHttpsCaching = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) { + rv = prefs->GetIntPref(HTTP_PREF("phishy-userpass-length"), &val); + if (NS_SUCCEEDED(rv)) + mPhishyUserPassLength = (uint8_t) clamped(val, 0, 0xff); + } + + if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnableSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.enabled.http2"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.http2"), &cVar); + if (NS_SUCCEEDED(rv)) + mHttp2Enabled = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.enabled.deps"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.deps"), &cVar); + if (NS_SUCCEEDED(rv)) + mUseH2Deps = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.enforce-tls-profile"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enforce-tls-profile"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnforceHttp2TlsProfile = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar); + if (NS_SUCCEEDED(rv)) + mCoalesceSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.persistent-settings"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.persistent-settings"), + &cVar); + if (NS_SUCCEEDED(rv)) + mSpdyPersistentSettings = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff)); + } + + if (PREF_CHANGED(HTTP_PREF("spdy.chunk-size"))) { + // keep this within http/2 ranges of 1 to 2^14-1 + rv = prefs->GetIntPref(HTTP_PREF("spdy.chunk-size"), &val); + if (NS_SUCCEEDED(rv)) + mSpdySendingChunkSize = (uint32_t) clamped(val, 1, 0x3fff); + } + + // The amount of idle seconds on a spdy connection before initiating a + // server ping. 0 will disable. + if (PREF_CHANGED(HTTP_PREF("spdy.ping-threshold"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.ping-threshold"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyPingThreshold = + PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff)); + } + + // The amount of seconds to wait for a spdy ping response before + // closing the session. + if (PREF_CHANGED(HTTP_PREF("spdy.ping-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.ping-timeout"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyPingTimeout = + PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff)); + } + + if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"), + &cVar); + if (NS_SUCCEEDED(rv)) + mAllowPush = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("altsvc.enabled"), + &cVar); + if (NS_SUCCEEDED(rv)) + mEnableAltSvc = cVar; + } + + + if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) { + rv = prefs->GetBoolPref(HTTP_PREF("altsvc.oe"), + &cVar); + if (NS_SUCCEEDED(rv)) + mEnableAltSvcOE = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.push-allowance"), &val); + if (NS_SUCCEEDED(rv)) { + mSpdyPushAllowance = + static_cast<uint32_t> + (clamped(val, 1024, static_cast<int32_t>(ASpdySession::kInitialRwin))); + } + } + + if (PREF_CHANGED(HTTP_PREF("spdy.pull-allowance"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.pull-allowance"), &val); + if (NS_SUCCEEDED(rv)) { + mSpdyPullAllowance = + static_cast<uint32_t>(clamped(val, 1024, 0x7fffffff)); + } + } + + if (PREF_CHANGED(HTTP_PREF("spdy.default-concurrent"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.default-concurrent"), &val); + if (NS_SUCCEEDED(rv)) { + mDefaultSpdyConcurrent = + static_cast<uint32_t>(std::max<int32_t>(std::min<int32_t>(val, 9999), 1)); + } + } + + // The amount of seconds to wait for a spdy ping response before + // closing the session. + if (PREF_CHANGED(HTTP_PREF("spdy.send-buffer-size"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.send-buffer-size"), &val); + if (NS_SUCCEEDED(rv)) + mSpdySendBufferSize = (uint32_t) clamped(val, 1500, 0x7fffffff); + } + + // The maximum amount of time to wait for socket transport to be + // established + if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("connection-timeout"), &val); + if (NS_SUCCEEDED(rv)) + // the pref is in seconds, but the variable is in milliseconds + mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC; + } + + // The maximum number of current global half open sockets allowable + // for starting a new speculative connection. + if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) { + rv = prefs->GetIntPref(HTTP_PREF("speculative-parallel-limit"), &val); + if (NS_SUCCEEDED(rv)) + mParallelSpeculativeConnectLimit = (uint32_t) clamped(val, 0, 1024); + } + + // Whether or not to block requests for non head js/css items (e.g. media) + // while those elements load. + if (PREF_CHANGED(HTTP_PREF("rendering-critical-requests-prioritization"))) { + rv = prefs->GetBoolPref(HTTP_PREF("rendering-critical-requests-prioritization"), &cVar); + if (NS_SUCCEEDED(rv)) + mCriticalRequestPrioritization = cVar; + } + + // on transition of network.http.diagnostics to true print + // a bunch of information to the console + if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) { + rv = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar); + if (NS_SUCCEEDED(rv) && cVar) { + if (mConnMgr) + mConnMgr->PrintDiagnostics(); + } + } + + if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) { + rv = prefs->GetIntPref(HTTP_PREF("max_response_header_size"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxHttpResponseHeaderSize = val; + } + } + // + // INTL options + // + + if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) { + nsCOMPtr<nsIPrefLocalizedString> pls; + prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(pls)); + if (pls) { + nsXPIDLString uval; + pls->ToString(getter_Copies(uval)); + if (uval) + SetAcceptLanguages(NS_ConvertUTF16toUTF8(uval).get()); + } + } + + // + // Tracking options + // + + if (PREF_CHANGED(DONOTTRACK_HEADER_ENABLED)) { + cVar = false; + rv = prefs->GetBoolPref(DONOTTRACK_HEADER_ENABLED, &cVar); + if (NS_SUCCEEDED(rv)) { + mDoNotTrackEnabled = cVar; + } + } + // Hint option + if (PREF_CHANGED(SAFE_HINT_HEADER_VALUE)) { + cVar = false; + rv = prefs->GetBoolPref(SAFE_HINT_HEADER_VALUE, &cVar); + if (NS_SUCCEEDED(rv)) { + mSafeHintEnabled = cVar; + } + } + + // toggle to true anytime a token bucket related pref is changed.. that + // includes telemetry and allow-experiments because of the abtest profile + bool requestTokenBucketUpdated = false; + + // + // Telemetry + // + + if (PREF_CHANGED(TELEMETRY_ENABLED)) { + cVar = false; + requestTokenBucketUpdated = true; + rv = prefs->GetBoolPref(TELEMETRY_ENABLED, &cVar); + if (NS_SUCCEEDED(rv)) { + mTelemetryEnabled = cVar; + } + } + + // "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256" is the required h2 interop + // suite. + + if (PREF_CHANGED(H2MANDATORY_SUITE)) { + cVar = false; + rv = prefs->GetBoolPref(H2MANDATORY_SUITE, &cVar); + if (NS_SUCCEEDED(rv)) { + mH2MandatorySuiteEnabled = cVar; + } + } + + // + // network.allow-experiments + // + if (PREF_CHANGED(ALLOW_EXPERIMENTS)) { + cVar = true; + requestTokenBucketUpdated = true; + rv = prefs->GetBoolPref(ALLOW_EXPERIMENTS, &cVar); + if (NS_SUCCEEDED(rv)) { + mAllowExperiments = cVar; + } + } + + // network.http.debug-observations + if (PREF_CHANGED("network.http.debug-observations")) { + cVar = false; + rv = prefs->GetBoolPref("network.http.debug-observations", &cVar); + if (NS_SUCCEEDED(rv)) { + mDebugObservations = cVar; + } + } + + // + // Test HTTP Pipelining (bug796192) + // If experiments are allowed and pipelining is Off, + // turn it On for just 10 minutes + // + if (mAllowExperiments && !mPipeliningEnabled && + PREF_CHANGED(HTTP_PREF("pipelining.abtest"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.abtest"), &cVar); + if (NS_SUCCEEDED(rv)) { + // If option is enabled, only test for ~1% of sessions + if (cVar && !(rand() % 128)) { + mCapabilities |= NS_HTTP_ALLOW_PIPELINING; + if (mPipelineTestTimer) + mPipelineTestTimer->Cancel(); + mPipelineTestTimer = + do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_SUCCEEDED(rv)) { + rv = mPipelineTestTimer->InitWithFuncCallback( + TimerCallback, this, 10*60*1000, // 10 minutes + nsITimer::TYPE_ONE_SHOT); + } + } else { + mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING; + if (mPipelineTestTimer) { + mPipelineTestTimer->Cancel(); + mPipelineTestTimer = nullptr; + } + } + } + } + + if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pacing.requests.enabled"), &cVar); + if (NS_SUCCEEDED(rv)) { + mRequestTokenBucketEnabled = cVar; + requestTokenBucketUpdated = true; + } + } + if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) { + rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.min-parallelism"), &val); + if (NS_SUCCEEDED(rv)) { + mRequestTokenBucketMinParallelism = static_cast<uint16_t>(clamped(val, 1, 1024)); + requestTokenBucketUpdated = true; + } + } + if (PREF_CHANGED(HTTP_PREF("pacing.requests.hz"))) { + rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.hz"), &val); + if (NS_SUCCEEDED(rv)) { + mRequestTokenBucketHz = static_cast<uint32_t>(clamped(val, 1, 10000)); + requestTokenBucketUpdated = true; + } + } + if (PREF_CHANGED(HTTP_PREF("pacing.requests.burst"))) { + rv = prefs->GetIntPref(HTTP_PREF("pacing.requests.burst"), &val); + if (NS_SUCCEEDED(rv)) { + mRequestTokenBucketBurst = val ? val : 1; + requestTokenBucketUpdated = true; + } + } + if (requestTokenBucketUpdated) { + MakeNewRequestTokenBucket(); + } + + // Keepalive values for initial and idle connections. + if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_connections"))) { + rv = prefs->GetBoolPref( + HTTP_PREF("tcp_keepalive.short_lived_connections"), &cVar); + if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveShortLivedEnabled) { + mTCPKeepaliveShortLivedEnabled = cVar; + } + } + + if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_time"))) { + rv = prefs->GetIntPref( + HTTP_PREF("tcp_keepalive.short_lived_time"), &val); + if (NS_SUCCEEDED(rv) && val > 0) + mTCPKeepaliveShortLivedTimeS = clamped(val, 1, 300); // Max 5 mins. + } + + if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.short_lived_idle_time"))) { + rv = prefs->GetIntPref( + HTTP_PREF("tcp_keepalive.short_lived_idle_time"), &val); + if (NS_SUCCEEDED(rv) && val > 0) + mTCPKeepaliveShortLivedIdleTimeS = clamped(val, + 1, kMaxTCPKeepIdle); + } + + // Keepalive values for Long-lived Connections. + if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_connections"))) { + rv = prefs->GetBoolPref( + HTTP_PREF("tcp_keepalive.long_lived_connections"), &cVar); + if (NS_SUCCEEDED(rv) && cVar != mTCPKeepaliveLongLivedEnabled) { + mTCPKeepaliveLongLivedEnabled = cVar; + } + } + + if (PREF_CHANGED(HTTP_PREF("tcp_keepalive.long_lived_idle_time"))) { + rv = prefs->GetIntPref( + HTTP_PREF("tcp_keepalive.long_lived_idle_time"), &val); + if (NS_SUCCEEDED(rv) && val > 0) + mTCPKeepaliveLongLivedIdleTimeS = clamped(val, + 1, kMaxTCPKeepIdle); + } + + if (PREF_CHANGED(HTTP_PREF("enforce-framing.http1")) || + PREF_CHANGED(HTTP_PREF("enforce-framing.soft")) ) { + rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.http1"), &cVar); + if (NS_SUCCEEDED(rv) && cVar) { + mEnforceH1Framing = FRAMECHECK_STRICT; + } else { + rv = prefs->GetBoolPref(HTTP_PREF("enforce-framing.soft"), &cVar); + if (NS_SUCCEEDED(rv) && cVar) { + mEnforceH1Framing = FRAMECHECK_BARELY; + } else { + mEnforceH1Framing = FRAMECHECK_LAX; + } + } + } + + // remote content-signature testing option + if (PREF_CHANGED(NEW_TAB_REMOTE_MODE)) { + nsAutoCString channel; + prefs->GetCharPref(NEW_TAB_REMOTE_MODE, getter_Copies(channel)); + if (channel.EqualsLiteral("test") || + channel.EqualsLiteral("test2") || + channel.EqualsLiteral("dev")) { + mNewTabContentSignaturesDisabled = true; + } else { + mNewTabContentSignaturesDisabled = false; + } + } + + if (PREF_CHANGED(HTTP_PREF("keep_empty_response_headers_as_empty_string"))) { + rv = prefs->GetBoolPref(HTTP_PREF("keep_empty_response_headers_as_empty_string"), + &cVar); + if (NS_SUCCEEDED(rv)) { + mKeepEmptyResponseHeadersAsEmtpyString = cVar; + } + } + + if (PREF_CHANGED(HTTP_PREF("spdy.hpack-default-buffer"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.default-hpack-buffer"), &val); + if (NS_SUCCEEDED(rv)) { + mDefaultHpackBuffer = val; + } + } + + // Enable HTTP response timeout if TCP Keepalives are disabled. + mResponseTimeoutEnabled = !mTCPKeepaliveShortLivedEnabled && + !mTCPKeepaliveLongLivedEnabled; + +#undef PREF_CHANGED +#undef MULTI_PREF_CHANGED +} + + +/** + * Static method called by mPipelineTestTimer when it expires. + */ +void +nsHttpHandler::TimerCallback(nsITimer * aTimer, void * aClosure) +{ + RefPtr<nsHttpHandler> thisObject = static_cast<nsHttpHandler*>(aClosure); + if (!thisObject->mPipeliningEnabled) + thisObject->mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING; +} + +/** + * Currently, only regularizes the case of subtags. + */ +static void +CanonicalizeLanguageTag(char *languageTag) +{ + char *s = languageTag; + while (*s != '\0') { + *s = nsCRT::ToLower(*s); + s++; + } + + s = languageTag; + bool isFirst = true; + bool seenSingleton = false; + while (*s != '\0') { + char *subTagEnd = strchr(s, '-'); + if (subTagEnd == nullptr) { + subTagEnd = strchr(s, '\0'); + } + + if (isFirst) { + isFirst = false; + } else if (seenSingleton) { + // Do nothing + } else { + size_t subTagLength = subTagEnd - s; + if (subTagLength == 1) { + seenSingleton = true; + } else if (subTagLength == 2) { + *s = nsCRT::ToUpper(*s); + *(s + 1) = nsCRT::ToUpper(*(s + 1)); + } else if (subTagLength == 4) { + *s = nsCRT::ToUpper(*s); + } + } + + s = subTagEnd; + if (*s != '\0') { + s++; + } + } +} + +/** + * Allocates a C string into that contains a ISO 639 language list + * notated with HTTP "q" values for output with a HTTP Accept-Language + * header. Previous q values will be stripped because the order of + * the langs imply the q value. The q values are calculated by dividing + * 1.0 amongst the number of languages present. + * + * Ex: passing: "en, ja" + * returns: "en,ja;q=0.5" + * + * passing: "en, ja, fr_CA" + * returns: "en,ja;q=0.7,fr_CA;q=0.3" + */ +static nsresult +PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLanguages) +{ + if (!i_AcceptLanguages) + return NS_OK; + + uint32_t n, count_n, size, wrote; + double q, dec; + char *p, *p2, *token, *q_Accept, *o_Accept; + const char *comma; + int32_t available; + + o_Accept = strdup(i_AcceptLanguages); + if (!o_Accept) + return NS_ERROR_OUT_OF_MEMORY; + for (p = o_Accept, n = size = 0; '\0' != *p; p++) { + if (*p == ',') n++; + size++; + } + + available = size + ++n * 11 + 1; + q_Accept = new char[available]; + if (!q_Accept) { + free(o_Accept); + return NS_ERROR_OUT_OF_MEMORY; + } + *q_Accept = '\0'; + q = 1.0; + dec = q / (double) n; + count_n = 0; + p2 = q_Accept; + for (token = nsCRT::strtok(o_Accept, ",", &p); + token != (char *) 0; + token = nsCRT::strtok(p, ",", &p)) + { + token = net_FindCharNotInSet(token, HTTP_LWS); + char* trim; + trim = net_FindCharInSet(token, ";" HTTP_LWS); + if (trim != (char*)0) // remove "; q=..." if present + *trim = '\0'; + + if (*token != '\0') { + CanonicalizeLanguageTag(token); + + comma = count_n++ != 0 ? "," : ""; // delimiter if not first item + uint32_t u = QVAL_TO_UINT(q); + + // Only display q-value if less than 1.00. + if (u < 100) { + const char *qval_str; + + // With a small number of languages, one decimal place is enough to prevent duplicate q-values. + // Also, trailing zeroes do not add any information, so they can be removed. + if ((n < 10) || ((u % 10) == 0)) { + u = (u + 5) / 10; + qval_str = "%s%s;q=0.%u"; + } else { + // Values below 10 require zero padding. + qval_str = "%s%s;q=0.%02u"; + } + + wrote = snprintf(p2, available, qval_str, comma, token, u); + } else { + wrote = snprintf(p2, available, "%s%s", comma, token); + } + + q -= dec; + p2 += wrote; + available -= wrote; + MOZ_ASSERT(available > 0, "allocated string not long enough"); + } + } + free(o_Accept); + + o_AcceptLanguages.Assign((const char *) q_Accept); + delete [] q_Accept; + + return NS_OK; +} + +nsresult +nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages) +{ + nsAutoCString buf; + nsresult rv = PrepareAcceptLanguages(aAcceptLanguages, buf); + if (NS_SUCCEEDED(rv)) + mAcceptLanguages.Assign(buf); + return rv; +} + +nsresult +nsHttpHandler::SetAccept(const char *aAccept) +{ + mAccept = aAccept; + return NS_OK; +} + +nsresult +nsHttpHandler::SetAcceptEncodings(const char *aAcceptEncodings, bool isSecure) +{ + if (isSecure) { + mHttpsAcceptEncodings = aAcceptEncodings; + } else { + // use legacy list if a secure override is not specified + mHttpAcceptEncodings = aAcceptEncodings; + if (mHttpsAcceptEncodings.IsEmpty()) { + mHttpsAcceptEncodings = aAcceptEncodings; + } + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsHttpHandler::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsHttpHandler, + nsIHttpProtocolHandler, + nsIProxiedProtocolHandler, + nsIProtocolHandler, + nsIObserver, + nsISupportsWeakReference, + nsISpeculativeConnect) + +//----------------------------------------------------------------------------- +// nsHttpHandler::nsIProtocolHandler +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsHttpHandler::GetScheme(nsACString &aScheme) +{ + aScheme.AssignLiteral("http"); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetDefaultPort(int32_t *result) +{ + *result = NS_HTTP_DEFAULT_PORT; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetProtocolFlags(uint32_t *result) +{ + *result = NS_HTTP_PROTOCOL_FLAGS; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::NewURI(const nsACString &aSpec, + const char *aCharset, + nsIURI *aBaseURI, + nsIURI **aURI) +{ + return mozilla::net::NewURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT, aURI); +} + +NS_IMETHODIMP +nsHttpHandler::NewChannel2(nsIURI* uri, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + LOG(("nsHttpHandler::NewChannel\n")); + + NS_ENSURE_ARG_POINTER(uri); + NS_ENSURE_ARG_POINTER(result); + + bool isHttp = false, isHttps = false; + + // Verify that we have been given a valid scheme + nsresult rv = uri->SchemeIs("http", &isHttp); + if (NS_FAILED(rv)) return rv; + if (!isHttp) { + rv = uri->SchemeIs("https", &isHttps); + if (NS_FAILED(rv)) return rv; + if (!isHttps) { + NS_WARNING("Invalid URI scheme"); + return NS_ERROR_UNEXPECTED; + } + } + + return NewProxiedChannel2(uri, nullptr, 0, nullptr, aLoadInfo, result); +} + +NS_IMETHODIMP +nsHttpHandler::NewChannel(nsIURI *uri, nsIChannel **result) +{ + return NewChannel2(uri, nullptr, result); +} + +NS_IMETHODIMP +nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsHttpHandler::nsIProxiedProtocolHandler +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsHttpHandler::NewProxiedChannel2(nsIURI *uri, + nsIProxyInfo* givenProxyInfo, + uint32_t proxyResolveFlags, + nsIURI *proxyURI, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + RefPtr<HttpBaseChannel> httpChannel; + + LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", + givenProxyInfo)); + + nsCOMPtr<nsProxyInfo> proxyInfo; + if (givenProxyInfo) { + proxyInfo = do_QueryInterface(givenProxyInfo); + NS_ENSURE_ARG(proxyInfo); + } + + bool https; + nsresult rv = uri->SchemeIs("https", &https); + if (NS_FAILED(rv)) + return rv; + + if (IsNeckoChild()) { + httpChannel = new HttpChannelChild(); + } else { + httpChannel = new nsHttpChannel(); + } + + uint32_t caps = mCapabilities; + + if (https) { + // enable pipelining over SSL if requested + if (mPipeliningOverSSL) + caps |= NS_HTTP_ALLOW_PIPELINING; + } + + if (!IsNeckoChild()) { + // HACK: make sure PSM gets initialized on the main thread. + net_EnsurePSMInit(); + } + + nsID channelId; + rv = NewChannelId(&channelId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel->Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI, channelId); + if (NS_FAILED(rv)) + return rv; + + // set the loadInfo on the new channel + rv = httpChannel->SetLoadInfo(aLoadInfo); + if (NS_FAILED(rv)) { + return rv; + } + + httpChannel.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::NewProxiedChannel(nsIURI *uri, + nsIProxyInfo* givenProxyInfo, + uint32_t proxyResolveFlags, + nsIURI *proxyURI, + nsIChannel **result) +{ + return NewProxiedChannel2(uri, givenProxyInfo, + proxyResolveFlags, proxyURI, + nullptr, result); +} + +//----------------------------------------------------------------------------- +// nsHttpHandler::nsIHttpProtocolHandler +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsHttpHandler::GetUserAgent(nsACString &value) +{ + value = UserAgent(); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetAppName(nsACString &value) +{ + value = mLegacyAppName; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetAppVersion(nsACString &value) +{ + value = mLegacyAppVersion; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetPlatform(nsACString &value) +{ + value = mPlatform; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetOscpu(nsACString &value) +{ + value = mOscpu; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpHandler::GetMisc(nsACString &value) +{ + value = mMisc; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsHttpHandler::nsIObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsHttpHandler::Observe(nsISupports *subject, + const char *topic, + const char16_t *data) +{ + MOZ_ASSERT(NS_IsMainThread()); + LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic)); + + if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject); + if (prefBranch) + PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get()); + } else if (!strcmp(topic, "profile-change-net-teardown") || + !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ) { + + mHandlerActive = false; + + // clear cache of all authentication credentials. + mAuthCache.ClearAll(); + mPrivateAuthCache.ClearAll(); + if (mWifiTickler) + mWifiTickler->Cancel(); + + // Inform nsIOService that network is tearing down. + gIOService->SetHttpHandlerAlreadyShutingDown(); + + ShutdownConnectionManager(); + + // need to reset the session start time since cache validation may + // depend on this value. + mSessionStartTime = NowInSeconds(); + + if (!mDoNotTrackEnabled) { + Telemetry::Accumulate(Telemetry::DNT_USAGE, 2); + } else { + Telemetry::Accumulate(Telemetry::DNT_USAGE, 1); + } + } else if (!strcmp(topic, "profile-change-net-restore")) { + // initialize connection manager + InitConnectionMgr(); + } else if (!strcmp(topic, "net:clear-active-logins")) { + mAuthCache.ClearAll(); + mPrivateAuthCache.ClearAll(); + } else if (!strcmp(topic, "net:prune-dead-connections")) { + if (mConnMgr) { + mConnMgr->PruneDeadConnections(); + } + } else if (!strcmp(topic, "net:prune-all-connections")) { + if (mConnMgr) { + mConnMgr->DoShiftReloadConnectionCleanup(nullptr); + mConnMgr->PruneDeadConnections(); + } + } else if (!strcmp(topic, "net:failed-to-process-uri-content")) { + nsCOMPtr<nsIURI> uri = do_QueryInterface(subject); + if (uri && mConnMgr) { + mConnMgr->ReportFailedToProcess(uri); + } + } else if (!strcmp(topic, "last-pb-context-exited")) { + mPrivateAuthCache.ClearAll(); + if (mConnMgr) { + mConnMgr->ClearAltServiceMappings(); + } + } else if (!strcmp(topic, "webapps-clear-data")) { + if (mConnMgr) { + mConnMgr->ClearAltServiceMappings(); + } + } else if (!strcmp(topic, "browser:purge-session-history")) { + if (mConnMgr) { + if (gSocketTransportService) { + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod(mConnMgr, + &nsHttpConnectionMgr::ClearConnectionHistory); + gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); + } + mConnMgr->ClearAltServiceMappings(); + } + } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { + nsAutoCString converted = NS_ConvertUTF16toUTF8(data); + if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) { + if (mConnMgr) { + mConnMgr->PruneDeadConnections(); + mConnMgr->VerifyTraffic(); + } + } + } else if (!strcmp(topic, "application-background")) { + // going to the background on android means we should close + // down idle connections for power conservation + if (mConnMgr) { + mConnMgr->DoShiftReloadConnectionCleanup(nullptr); + } + } + + return NS_OK; +} + +// nsISpeculativeConnect + +nsresult +nsHttpHandler::SpeculativeConnectInternal(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks, + bool anonymous) +{ + if (IsNeckoChild()) { + ipc::URIParams params; + SerializeURI(aURI, params); + gNeckoChild->SendSpeculativeConnect(params, + IPC::Principal(aPrincipal), + anonymous); + return NS_OK; + } + + if (!mHandlerActive) + return NS_OK; + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + if (mDebugObservations && obsService) { + // this is basically used for test coverage of an otherwise 'hintable' + // feature + obsService->NotifyObservers(nullptr, "speculative-connect-request", + nullptr); + if (!IsNeckoChild()) { + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + Unused << neckoParent->SendSpeculativeConnectRequest(); + } + } + } + + nsISiteSecurityService* sss = gHttpHandler->GetSSService(); + bool isStsHost = false; + if (!sss) + return NS_OK; + + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks); + uint32_t flags = 0; + if (loadContext && loadContext->UsePrivateBrowsing()) + flags |= nsISocketProvider::NO_PERMANENT_STORAGE; + nsCOMPtr<nsIURI> clone; + if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, + aURI, flags, nullptr, &isStsHost)) && + isStsHost) { + if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI, + getter_AddRefs(clone)))) { + aURI = clone.get(); + // (NOTE: We better make sure |clone| stays alive until the end + // of the function now, since our aURI arg now points to it!) + } + } + + nsAutoCString scheme; + nsresult rv = aURI->GetScheme(scheme); + if (NS_FAILED(rv)) + return rv; + + // If this is HTTPS, make sure PSM is initialized as the channel + // creation path may have been bypassed + if (scheme.EqualsLiteral("https")) { + if (!IsNeckoChild()) { + // make sure PSM gets initialized on the main thread. + net_EnsurePSMInit(); + } + } + // Ensure that this is HTTP or HTTPS, otherwise we don't do preconnect here + else if (!scheme.EqualsLiteral("http")) + return NS_ERROR_UNEXPECTED; + + // Construct connection info object + bool usingSSL = false; + rv = aURI->SchemeIs("https", &usingSSL); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString host; + rv = aURI->GetAsciiHost(host); + if (NS_FAILED(rv)) + return rv; + + int32_t port = -1; + rv = aURI->GetPort(&port); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString username; + aURI->GetUsername(username); + + NeckoOriginAttributes neckoOriginAttributes; + // If the principal is given, we use the originAttributes from this + // principal. Otherwise, we use the originAttributes from the + // loadContext. + if (aPrincipal) { + neckoOriginAttributes.InheritFromDocToNecko( + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()); + } else if (loadContext) { + DocShellOriginAttributes docshellOriginAttributes; + loadContext->GetOriginAttributes(docshellOriginAttributes); + neckoOriginAttributes.InheritFromDocShellToNecko(docshellOriginAttributes); + } + + auto *ci = + new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr, + neckoOriginAttributes, usingSSL); + ci->SetAnonymous(anonymous); + + return SpeculativeConnect(ci, aCallbacks); +} + +NS_IMETHODIMP +nsHttpHandler::SpeculativeConnect(nsIURI *aURI, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false); +} + +NS_IMETHODIMP +nsHttpHandler::SpeculativeConnect2(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false); +} + +NS_IMETHODIMP +nsHttpHandler::SpeculativeAnonymousConnect(nsIURI *aURI, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true); +} + +NS_IMETHODIMP +nsHttpHandler::SpeculativeAnonymousConnect2(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true); +} + +void +nsHttpHandler::TickleWifi(nsIInterfaceRequestor *cb) +{ + if (!cb || !mWifiTickler) + return; + + // If B2G requires a similar mechanism nsINetworkManager, currently only avail + // on B2G, contains the necessary information on wifi and gateway + + nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(cb); + nsCOMPtr<nsPIDOMWindowOuter> piWindow = do_QueryInterface(domWindow); + if (!piWindow) + return; + + nsCOMPtr<nsIDOMNavigator> domNavigator = piWindow->GetNavigator(); + nsCOMPtr<nsIMozNavigatorNetwork> networkNavigator = + do_QueryInterface(domNavigator); + if (!networkNavigator) + return; + + nsCOMPtr<nsINetworkProperties> networkProperties; + networkNavigator->GetProperties(getter_AddRefs(networkProperties)); + if (!networkProperties) + return; + + uint32_t gwAddress; + bool isWifi; + nsresult rv; + + rv = networkProperties->GetDhcpGateway(&gwAddress); + if (NS_SUCCEEDED(rv)) + rv = networkProperties->GetIsWifi(&isWifi); + if (NS_FAILED(rv)) + return; + + if (!gwAddress || !isWifi) + return; + + mWifiTickler->SetIPV4Address(gwAddress); + mWifiTickler->Tickle(); +} + +//----------------------------------------------------------------------------- +// nsHttpsHandler implementation +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsHttpsHandler, + nsIHttpProtocolHandler, + nsIProxiedProtocolHandler, + nsIProtocolHandler, + nsISupportsWeakReference, + nsISpeculativeConnect) + +nsresult +nsHttpsHandler::Init() +{ + nsCOMPtr<nsIProtocolHandler> httpHandler( + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http")); + MOZ_ASSERT(httpHandler.get() != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpsHandler::GetScheme(nsACString &aScheme) +{ + aScheme.AssignLiteral("https"); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpsHandler::GetDefaultPort(int32_t *aPort) +{ + *aPort = NS_HTTPS_DEFAULT_PORT; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpsHandler::GetProtocolFlags(uint32_t *aProtocolFlags) +{ + *aProtocolFlags = NS_HTTP_PROTOCOL_FLAGS | URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpsHandler::NewURI(const nsACString &aSpec, + const char *aOriginCharset, + nsIURI *aBaseURI, + nsIURI **_retval) +{ + return mozilla::net::NewURI(aSpec, aOriginCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT, _retval); +} + +NS_IMETHODIMP +nsHttpsHandler::NewChannel2(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIChannel** _retval) +{ + MOZ_ASSERT(gHttpHandler); + if (!gHttpHandler) + return NS_ERROR_UNEXPECTED; + return gHttpHandler->NewChannel2(aURI, aLoadInfo, _retval); +} + +NS_IMETHODIMP +nsHttpsHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NewChannel2(aURI, nullptr, _retval); +} + +NS_IMETHODIMP +nsHttpsHandler::AllowPort(int32_t aPort, const char *aScheme, bool *_retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +void +nsHttpHandler::ShutdownConnectionManager() +{ + // ensure connection manager is shutdown + if (mConnMgr) { + mConnMgr->Shutdown(); + } +} + +nsresult +nsHttpHandler::NewChannelId(nsID *channelId) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!mUUIDGen) { + nsresult rv; + mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mUUIDGen->GenerateUUIDInPlace(channelId); +} + +} // namespace net +} // namespace mozilla |