netwerk/protocol/http/nsHttpChannel.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim:set expandtab ts=4 sw=4 sts=4 cin: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // HttpLog.h should generally be included first
michael@0 8 #include "HttpLog.h"
michael@0 9
michael@0 10 #include "nsHttp.h"
michael@0 11 #include "nsHttpChannel.h"
michael@0 12 #include "nsHttpHandler.h"
michael@0 13 #include "nsIApplicationCacheService.h"
michael@0 14 #include "nsIApplicationCacheContainer.h"
michael@0 15 #include "nsICacheStorageService.h"
michael@0 16 #include "nsICacheStorage.h"
michael@0 17 #include "nsICacheEntry.h"
michael@0 18 #include "nsICryptoHash.h"
michael@0 19 #include "nsIStringBundle.h"
michael@0 20 #include "nsIStreamListenerTee.h"
michael@0 21 #include "nsISeekableStream.h"
michael@0 22 #include "nsILoadGroupChild.h"
michael@0 23 #include "nsIProtocolProxyService2.h"
michael@0 24 #include "nsMimeTypes.h"
michael@0 25 #include "nsNetUtil.h"
michael@0 26 #include "prprf.h"
michael@0 27 #include "prnetdb.h"
michael@0 28 #include "nsEscape.h"
michael@0 29 #include "nsStreamUtils.h"
michael@0 30 #include "nsIOService.h"
michael@0 31 #include "nsDNSPrefetch.h"
michael@0 32 #include "nsChannelClassifier.h"
michael@0 33 #include "nsIRedirectResultListener.h"
michael@0 34 #include "mozilla/TimeStamp.h"
michael@0 35 #include "nsError.h"
michael@0 36 #include "nsPrintfCString.h"
michael@0 37 #include "nsAlgorithm.h"
michael@0 38 #include "GeckoProfiler.h"
michael@0 39 #include "nsIConsoleService.h"
michael@0 40 #include "mozilla/Attributes.h"
michael@0 41 #include "mozilla/VisualEventTracer.h"
michael@0 42 #include "nsISSLSocketControl.h"
michael@0 43 #include "sslt.h"
michael@0 44 #include "nsContentUtils.h"
michael@0 45 #include "nsIPermissionManager.h"
michael@0 46 #include "nsIPrincipal.h"
michael@0 47 #include "nsIScriptSecurityManager.h"
michael@0 48 #include "nsISSLStatus.h"
michael@0 49 #include "nsISSLStatusProvider.h"
michael@0 50 #include "LoadContextInfo.h"
michael@0 51 #include "netCore.h"
michael@0 52 #include "nsHttpTransaction.h"
michael@0 53 #include "nsICacheEntryDescriptor.h"
michael@0 54 #include "nsICancelable.h"
michael@0 55 #include "nsIHttpChannelAuthProvider.h"
michael@0 56 #include "nsIHttpEventSink.h"
michael@0 57 #include "nsIPrompt.h"
michael@0 58 #include "nsInputStreamPump.h"
michael@0 59 #include "nsURLHelper.h"
michael@0 60 #include "nsISocketTransport.h"
michael@0 61 #include "nsICacheSession.h"
michael@0 62 #include "nsIStreamConverterService.h"
michael@0 63 #include "nsISiteSecurityService.h"
michael@0 64 #include "nsCRT.h"
michael@0 65 #include "nsPIDOMWindow.h"
michael@0 66 #include "nsPerformance.h"
michael@0 67 #include "CacheObserver.h"
michael@0 68 #include "mozilla/Telemetry.h"
michael@0 69 #include "mozIThirdPartyUtil.h"
michael@0 70
michael@0 71 namespace mozilla { namespace net {
michael@0 72
michael@0 73 namespace {
michael@0 74
michael@0 75 // True if the local cache should be bypassed when processing a request.
michael@0 76 #define BYPASS_LOCAL_CACHE(loadFlags) \
michael@0 77 (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
michael@0 78 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
michael@0 79
michael@0 80 #define CACHE_FILE_GONE(result) \
michael@0 81 ((result) == NS_ERROR_FILE_NOT_FOUND || \
michael@0 82 (result) == NS_ERROR_FILE_CORRUPTED)
michael@0 83
michael@0 84 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
michael@0 85 static NS_DEFINE_CID(kStreamTransportServiceCID,
michael@0 86 NS_STREAMTRANSPORTSERVICE_CID);
michael@0 87
michael@0 88 enum CacheDisposition {
michael@0 89 kCacheHit = 1,
michael@0 90 kCacheHitViaReval = 2,
michael@0 91 kCacheMissedViaReval = 3,
michael@0 92 kCacheMissed = 4
michael@0 93 };
michael@0 94
michael@0 95 void
michael@0 96 AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss)
michael@0 97 {
michael@0 98 if (!CacheObserver::UseNewCache()) {
michael@0 99 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss);
michael@0 100 }
michael@0 101 else {
michael@0 102 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
michael@0 103
michael@0 104 int32_t experiment = CacheObserver::HalfLifeExperiment();
michael@0 105 if (experiment > 0 && hitOrMiss == kCacheMissed) {
michael@0 106 Telemetry::Accumulate(Telemetry::HTTP_CACHE_MISS_HALFLIFE_EXPERIMENT,
michael@0 107 experiment - 1);
michael@0 108 }
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 // Computes and returns a SHA1 hash of the input buffer. The input buffer
michael@0 113 // must be a null-terminated string.
michael@0 114 nsresult
michael@0 115 Hash(const char *buf, nsACString &hash)
michael@0 116 {
michael@0 117 nsresult rv;
michael@0 118
michael@0 119 nsCOMPtr<nsICryptoHash> hasher
michael@0 120 = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
michael@0 121 NS_ENSURE_SUCCESS(rv, rv);
michael@0 122
michael@0 123 rv = hasher->Init(nsICryptoHash::SHA1);
michael@0 124 NS_ENSURE_SUCCESS(rv, rv);
michael@0 125
michael@0 126 rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf),
michael@0 127 strlen(buf));
michael@0 128 NS_ENSURE_SUCCESS(rv, rv);
michael@0 129
michael@0 130 rv = hasher->Finish(true, hash);
michael@0 131 NS_ENSURE_SUCCESS(rv, rv);
michael@0 132
michael@0 133 return NS_OK;
michael@0 134 }
michael@0 135
michael@0 136 bool IsRedirectStatus(uint32_t status)
michael@0 137 {
michael@0 138 // 305 disabled as a security measure (see bug 187996).
michael@0 139 return status == 300 || status == 301 || status == 302 || status == 303 ||
michael@0 140 status == 307 || status == 308;
michael@0 141 }
michael@0 142
michael@0 143 // We only treat 3xx responses as redirects if they have a Location header and
michael@0 144 // the status code is in a whitelist.
michael@0 145 bool
michael@0 146 WillRedirect(const nsHttpResponseHead * response)
michael@0 147 {
michael@0 148 return IsRedirectStatus(response->Status()) &&
michael@0 149 response->PeekHeader(nsHttp::Location);
michael@0 150 }
michael@0 151
michael@0 152 } // unnamed namespace
michael@0 153
michael@0 154 class AutoRedirectVetoNotifier
michael@0 155 {
michael@0 156 public:
michael@0 157 AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel)
michael@0 158 {
michael@0 159 if (mChannel->mHasAutoRedirectVetoNotifier) {
michael@0 160 MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
michael@0 161 mChannel = nullptr;
michael@0 162 return;
michael@0 163 }
michael@0 164
michael@0 165 mChannel->mHasAutoRedirectVetoNotifier = true;
michael@0 166 }
michael@0 167 ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);}
michael@0 168 void RedirectSucceeded() {ReportRedirectResult(true);}
michael@0 169
michael@0 170 private:
michael@0 171 nsHttpChannel* mChannel;
michael@0 172 void ReportRedirectResult(bool succeeded);
michael@0 173 };
michael@0 174
michael@0 175 void
michael@0 176 AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
michael@0 177 {
michael@0 178 if (!mChannel)
michael@0 179 return;
michael@0 180
michael@0 181 mChannel->mRedirectChannel = nullptr;
michael@0 182
michael@0 183 nsCOMPtr<nsIRedirectResultListener> vetoHook;
michael@0 184 NS_QueryNotificationCallbacks(mChannel,
michael@0 185 NS_GET_IID(nsIRedirectResultListener),
michael@0 186 getter_AddRefs(vetoHook));
michael@0 187
michael@0 188 nsHttpChannel* channel = mChannel;
michael@0 189 mChannel = nullptr;
michael@0 190
michael@0 191 if (vetoHook)
michael@0 192 vetoHook->OnRedirectResult(succeeded);
michael@0 193
michael@0 194 // Drop after the notification
michael@0 195 channel->mHasAutoRedirectVetoNotifier = false;
michael@0 196
michael@0 197 MOZ_EVENT_TRACER_DONE(channel, "net::http::redirect-callbacks");
michael@0 198 }
michael@0 199
michael@0 200 //-----------------------------------------------------------------------------
michael@0 201 // nsHttpChannel <public>
michael@0 202 //-----------------------------------------------------------------------------
michael@0 203
michael@0 204 nsHttpChannel::nsHttpChannel()
michael@0 205 : HttpAsyncAborter<nsHttpChannel>(MOZ_THIS_IN_INITIALIZER_LIST())
michael@0 206 , mLogicalOffset(0)
michael@0 207 , mPostID(0)
michael@0 208 , mRequestTime(0)
michael@0 209 , mOfflineCacheLastModifiedTime(0)
michael@0 210 , mCachedContentIsValid(false)
michael@0 211 , mCachedContentIsPartial(false)
michael@0 212 , mTransactionReplaced(false)
michael@0 213 , mAuthRetryPending(false)
michael@0 214 , mProxyAuthPending(false)
michael@0 215 , mResuming(false)
michael@0 216 , mInitedCacheEntry(false)
michael@0 217 , mFallbackChannel(false)
michael@0 218 , mCustomConditionalRequest(false)
michael@0 219 , mFallingBack(false)
michael@0 220 , mWaitingForRedirectCallback(false)
michael@0 221 , mRequestTimeInitialized(false)
michael@0 222 , mCacheEntryIsReadOnly(false)
michael@0 223 , mCacheEntryIsWriteOnly(false)
michael@0 224 , mCacheEntriesToWaitFor(0)
michael@0 225 , mHasQueryString(0)
michael@0 226 , mConcurentCacheAccess(0)
michael@0 227 , mIsPartialRequest(0)
michael@0 228 , mHasAutoRedirectVetoNotifier(0)
michael@0 229 , mDidReval(false)
michael@0 230 , mForcePending(false)
michael@0 231 {
michael@0 232 LOG(("Creating nsHttpChannel [this=%p]\n", this));
michael@0 233 mChannelCreationTime = PR_Now();
michael@0 234 mChannelCreationTimestamp = TimeStamp::Now();
michael@0 235 }
michael@0 236
michael@0 237 nsHttpChannel::~nsHttpChannel()
michael@0 238 {
michael@0 239 LOG(("Destroying nsHttpChannel [this=%p]\n", this));
michael@0 240
michael@0 241 if (mAuthProvider)
michael@0 242 mAuthProvider->Disconnect(NS_ERROR_ABORT);
michael@0 243 }
michael@0 244
michael@0 245 nsresult
michael@0 246 nsHttpChannel::Init(nsIURI *uri,
michael@0 247 uint32_t caps,
michael@0 248 nsProxyInfo *proxyInfo,
michael@0 249 uint32_t proxyResolveFlags,
michael@0 250 nsIURI *proxyURI)
michael@0 251 {
michael@0 252 #ifdef MOZ_VISUAL_EVENT_TRACER
michael@0 253 nsAutoCString url;
michael@0 254 uri->GetAsciiSpec(url);
michael@0 255 MOZ_EVENT_TRACER_NAME_OBJECT(this, url.get());
michael@0 256 #endif
michael@0 257
michael@0 258 nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo,
michael@0 259 proxyResolveFlags, proxyURI);
michael@0 260 if (NS_FAILED(rv))
michael@0 261 return rv;
michael@0 262
michael@0 263 LOG(("nsHttpChannel::Init [this=%p]\n", this));
michael@0 264
michael@0 265 return rv;
michael@0 266 }
michael@0 267 //-----------------------------------------------------------------------------
michael@0 268 // nsHttpChannel <private>
michael@0 269 //-----------------------------------------------------------------------------
michael@0 270
michael@0 271 nsresult
michael@0 272 nsHttpChannel::Connect()
michael@0 273 {
michael@0 274 nsresult rv;
michael@0 275
michael@0 276 LOG(("nsHttpChannel::Connect [this=%p]\n", this));
michael@0 277
michael@0 278 // Even if we're in private browsing mode, we still enforce existing STS
michael@0 279 // data (it is read-only).
michael@0 280 // if the connection is not using SSL and either the exact host matches or
michael@0 281 // a superdomain wants to force HTTPS, do it.
michael@0 282 bool usingSSL = false;
michael@0 283 rv = mURI->SchemeIs("https", &usingSSL);
michael@0 284 NS_ENSURE_SUCCESS(rv,rv);
michael@0 285
michael@0 286 if (!usingSSL) {
michael@0 287 // enforce Strict-Transport-Security
michael@0 288 nsISiteSecurityService* sss = gHttpHandler->GetSSService();
michael@0 289 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
michael@0 290
michael@0 291 bool isStsHost = false;
michael@0 292 uint32_t flags = mPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
michael@0 293 rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
michael@0 294 &isStsHost);
michael@0 295
michael@0 296 // if the SSS check fails, it's likely because this load is on a
michael@0 297 // malformed URI or something else in the setup is wrong, so any error
michael@0 298 // should be reported.
michael@0 299 NS_ENSURE_SUCCESS(rv, rv);
michael@0 300
michael@0 301 if (isStsHost) {
michael@0 302 LOG(("nsHttpChannel::Connect() STS permissions found\n"));
michael@0 303 return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 // ensure that we are using a valid hostname
michael@0 308 if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
michael@0 309 return NS_ERROR_UNKNOWN_HOST;
michael@0 310
michael@0 311 // Finalize ConnectionInfo flags before SpeculativeConnect
michael@0 312 mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
michael@0 313 mConnectionInfo->SetPrivate(mPrivateBrowsing);
michael@0 314
michael@0 315 // Consider opening a TCP connection right away
michael@0 316 RetrieveSSLOptions();
michael@0 317 SpeculativeConnect();
michael@0 318
michael@0 319 // Don't allow resuming when cache must be used
michael@0 320 if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
michael@0 321 LOG(("Resuming from cache is not supported yet"));
michael@0 322 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 323 }
michael@0 324
michael@0 325 if (!gHttpHandler->UseCache()) {
michael@0 326 return ContinueConnect();
michael@0 327 }
michael@0 328
michael@0 329 // open a cache entry for this channel...
michael@0 330 rv = OpenCacheEntry(usingSSL);
michael@0 331
michael@0 332 // do not continue if asyncOpenCacheEntry is in progress
michael@0 333 if (mCacheEntriesToWaitFor) {
michael@0 334 MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
michael@0 335 return NS_OK;
michael@0 336 }
michael@0 337
michael@0 338 if (NS_FAILED(rv)) {
michael@0 339 LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
michael@0 340 // if this channel is only allowed to pull from the cache, then
michael@0 341 // we must fail if we were unable to open a cache entry.
michael@0 342 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
michael@0 343 // If we have a fallback URI (and we're not already
michael@0 344 // falling back), process the fallback asynchronously.
michael@0 345 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
michael@0 346 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
michael@0 347 }
michael@0 348 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 349 }
michael@0 350 // otherwise, let's just proceed without using the cache.
michael@0 351 }
michael@0 352
michael@0 353 return ContinueConnect();
michael@0 354 }
michael@0 355
michael@0 356 nsresult
michael@0 357 nsHttpChannel::ContinueConnect()
michael@0 358 {
michael@0 359 // we may or may not have a cache entry at this point
michael@0 360 if (mCacheEntry) {
michael@0 361 // read straight from the cache if possible...
michael@0 362 if (mCachedContentIsValid) {
michael@0 363 nsRunnableMethod<nsHttpChannel> *event = nullptr;
michael@0 364 if (!mCachedContentIsPartial) {
michael@0 365 AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
michael@0 366 }
michael@0 367 nsresult rv = ReadFromCache(true);
michael@0 368 if (NS_FAILED(rv) && event) {
michael@0 369 event->Revoke();
michael@0 370 }
michael@0 371
michael@0 372 AccumulateCacheHitTelemetry(kCacheHit);
michael@0 373
michael@0 374 return rv;
michael@0 375 }
michael@0 376 else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
michael@0 377 // the cache contains the requested resource, but it must be
michael@0 378 // validated before we can reuse it. since we are not allowed
michael@0 379 // to hit the net, there's nothing more to do. the document
michael@0 380 // is effectively not in the cache.
michael@0 381 LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
michael@0 382 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 383 }
michael@0 384 }
michael@0 385 else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
michael@0 386 // If we have a fallback URI (and we're not already
michael@0 387 // falling back), process the fallback asynchronously.
michael@0 388 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
michael@0 389 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
michael@0 390 }
michael@0 391 LOG((" !mCachedEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
michael@0 392 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 393 }
michael@0 394
michael@0 395 if (mLoadFlags & LOAD_NO_NETWORK_IO) {
michael@0 396 LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
michael@0 397 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 398 }
michael@0 399
michael@0 400 // hit the net...
michael@0 401 nsresult rv = SetupTransaction();
michael@0 402 if (NS_FAILED(rv)) return rv;
michael@0 403
michael@0 404 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
michael@0 405 if (NS_FAILED(rv)) return rv;
michael@0 406
michael@0 407 rv = mTransactionPump->AsyncRead(this, nullptr);
michael@0 408 if (NS_FAILED(rv)) return rv;
michael@0 409
michael@0 410 uint32_t suspendCount = mSuspendCount;
michael@0 411 while (suspendCount--)
michael@0 412 mTransactionPump->Suspend();
michael@0 413
michael@0 414 return NS_OK;
michael@0 415 }
michael@0 416
michael@0 417 void
michael@0 418 nsHttpChannel::SpeculativeConnect()
michael@0 419 {
michael@0 420 // Before we take the latency hit of dealing with the cache, try and
michael@0 421 // get the TCP (and SSL) handshakes going so they can overlap.
michael@0 422
michael@0 423 // don't speculate on uses of the offline application cache,
michael@0 424 // if we are offline, when doing http upgrade (i.e. websockets bootstrap),
michael@0 425 // or if we can't do keep-alive (because then we couldn't reuse
michael@0 426 // the speculative connection anyhow).
michael@0 427 if (mApplicationCache || gIOService->IsOffline() ||
michael@0 428 mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
michael@0 429 return;
michael@0 430
michael@0 431 // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
michael@0 432 // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
michael@0 433 // so skip preconnects for them.
michael@0 434 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
michael@0 435 LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
michael@0 436 return;
michael@0 437
michael@0 438 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 439 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
michael@0 440 getter_AddRefs(callbacks));
michael@0 441 if (!callbacks)
michael@0 442 return;
michael@0 443
michael@0 444 gHttpHandler->SpeculativeConnect(
michael@0 445 mConnectionInfo, callbacks,
michael@0 446 mCaps & (NS_HTTP_ALLOW_RSA_FALSESTART | NS_HTTP_DISALLOW_SPDY));
michael@0 447 }
michael@0 448
michael@0 449 void
michael@0 450 nsHttpChannel::DoNotifyListenerCleanup()
michael@0 451 {
michael@0 452 // We don't need this info anymore
michael@0 453 CleanRedirectCacheChainIfNecessary();
michael@0 454 }
michael@0 455
michael@0 456 void
michael@0 457 nsHttpChannel::HandleAsyncRedirect()
michael@0 458 {
michael@0 459 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
michael@0 460
michael@0 461 if (mSuspendCount) {
michael@0 462 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
michael@0 463 mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
michael@0 464 return;
michael@0 465 }
michael@0 466
michael@0 467 nsresult rv = NS_OK;
michael@0 468
michael@0 469 LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
michael@0 470
michael@0 471 // since this event is handled asynchronously, it is possible that this
michael@0 472 // channel could have been canceled, in which case there would be no point
michael@0 473 // in processing the redirect.
michael@0 474 if (NS_SUCCEEDED(mStatus)) {
michael@0 475 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
michael@0 476 rv = AsyncProcessRedirection(mResponseHead->Status());
michael@0 477 if (NS_FAILED(rv)) {
michael@0 478 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
michael@0 479 // TODO: if !DoNotRender3xxBody(), render redirect body instead.
michael@0 480 // But first we need to cache 3xx bodies (bug 748510)
michael@0 481 ContinueHandleAsyncRedirect(rv);
michael@0 482 }
michael@0 483 }
michael@0 484 else {
michael@0 485 ContinueHandleAsyncRedirect(NS_OK);
michael@0 486 }
michael@0 487 }
michael@0 488
michael@0 489 nsresult
michael@0 490 nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv)
michael@0 491 {
michael@0 492 if (NS_FAILED(rv)) {
michael@0 493 // If AsyncProcessRedirection fails, then we have to send out the
michael@0 494 // OnStart/OnStop notifications.
michael@0 495 LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv));
michael@0 496 mStatus = rv;
michael@0 497 DoNotifyListener();
michael@0 498 }
michael@0 499
michael@0 500 // close the cache entry. Blow it away if we couldn't process the redirect
michael@0 501 // for some reason (the cache entry might be corrupt).
michael@0 502 if (mCacheEntry) {
michael@0 503 if (NS_FAILED(rv))
michael@0 504 mCacheEntry->AsyncDoom(nullptr);
michael@0 505 }
michael@0 506 CloseCacheEntry(false);
michael@0 507
michael@0 508 mIsPending = false;
michael@0 509
michael@0 510 if (mLoadGroup)
michael@0 511 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
michael@0 512
michael@0 513 return NS_OK;
michael@0 514 }
michael@0 515
michael@0 516 void
michael@0 517 nsHttpChannel::HandleAsyncNotModified()
michael@0 518 {
michael@0 519 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
michael@0 520
michael@0 521 if (mSuspendCount) {
michael@0 522 LOG(("Waiting until resume to do async not-modified [this=%p]\n",
michael@0 523 this));
michael@0 524 mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
michael@0 525 return;
michael@0 526 }
michael@0 527
michael@0 528 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
michael@0 529
michael@0 530 DoNotifyListener();
michael@0 531
michael@0 532 CloseCacheEntry(true);
michael@0 533
michael@0 534 mIsPending = false;
michael@0 535
michael@0 536 if (mLoadGroup)
michael@0 537 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
michael@0 538 }
michael@0 539
michael@0 540 void
michael@0 541 nsHttpChannel::HandleAsyncFallback()
michael@0 542 {
michael@0 543 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
michael@0 544
michael@0 545 if (mSuspendCount) {
michael@0 546 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
michael@0 547 mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
michael@0 548 return;
michael@0 549 }
michael@0 550
michael@0 551 nsresult rv = NS_OK;
michael@0 552
michael@0 553 LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
michael@0 554
michael@0 555 // since this event is handled asynchronously, it is possible that this
michael@0 556 // channel could have been canceled, in which case there would be no point
michael@0 557 // in processing the fallback.
michael@0 558 if (!mCanceled) {
michael@0 559 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
michael@0 560 bool waitingForRedirectCallback;
michael@0 561 rv = ProcessFallback(&waitingForRedirectCallback);
michael@0 562 if (waitingForRedirectCallback)
michael@0 563 return;
michael@0 564 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
michael@0 565 }
michael@0 566
michael@0 567 ContinueHandleAsyncFallback(rv);
michael@0 568 }
michael@0 569
michael@0 570 nsresult
michael@0 571 nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
michael@0 572 {
michael@0 573 if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
michael@0 574 // If ProcessFallback fails, then we have to send out the
michael@0 575 // OnStart/OnStop notifications.
michael@0 576 LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
michael@0 577 mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 578 DoNotifyListener();
michael@0 579 }
michael@0 580
michael@0 581 mIsPending = false;
michael@0 582
michael@0 583 if (mLoadGroup)
michael@0 584 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
michael@0 585
michael@0 586 return rv;
michael@0 587 }
michael@0 588
michael@0 589 void
michael@0 590 nsHttpChannel::SetupTransactionLoadGroupInfo()
michael@0 591 {
michael@0 592 // Find the loadgroup at the end of the chain in order
michael@0 593 // to make sure all channels derived from the load group
michael@0 594 // use the same connection scope.
michael@0 595 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
michael@0 596 if (!childLoadGroup)
michael@0 597 return;
michael@0 598
michael@0 599 nsCOMPtr<nsILoadGroup> rootLoadGroup;
michael@0 600 childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
michael@0 601 if (!rootLoadGroup)
michael@0 602 return;
michael@0 603
michael@0 604 // Set the load group connection scope on the transaction
michael@0 605 nsCOMPtr<nsILoadGroupConnectionInfo> ci;
michael@0 606 rootLoadGroup->GetConnectionInfo(getter_AddRefs(ci));
michael@0 607 if (ci)
michael@0 608 mTransaction->SetLoadGroupConnectionInfo(ci);
michael@0 609 }
michael@0 610
michael@0 611 void
michael@0 612 nsHttpChannel::RetrieveSSLOptions()
michael@0 613 {
michael@0 614 if (!IsHTTPS() || mPrivateBrowsing)
michael@0 615 return;
michael@0 616
michael@0 617 nsIPrincipal *principal = GetPrincipal();
michael@0 618 if (!principal)
michael@0 619 return;
michael@0 620
michael@0 621 nsCOMPtr<nsIPermissionManager> permMgr =
michael@0 622 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
michael@0 623 if (!permMgr)
michael@0 624 return;
michael@0 625
michael@0 626 uint32_t perm;
michael@0 627 nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
michael@0 628 "falsestart-rsa", &perm);
michael@0 629 if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) {
michael@0 630 LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] "
michael@0 631 "falsestart-rsa permission found\n", this));
michael@0 632 mCaps |= NS_HTTP_ALLOW_RSA_FALSESTART;
michael@0 633 }
michael@0 634 }
michael@0 635
michael@0 636 static bool
michael@0 637 SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
michael@0 638 const nsCString &methodString)
michael@0 639 {
michael@0 640 if (method == nsHttpRequestHead::kMethod_Get ||
michael@0 641 method == nsHttpRequestHead::kMethod_Head ||
michael@0 642 method == nsHttpRequestHead::kMethod_Options) {
michael@0 643 return true;
michael@0 644 }
michael@0 645
michael@0 646 if (method != nsHttpRequestHead::kMethod_Custom) {
michael@0 647 return false;
michael@0 648 }
michael@0 649
michael@0 650 return (!strcmp(methodString.get(), "PROPFIND") ||
michael@0 651 !strcmp(methodString.get(), "PROPPATCH"));
michael@0 652 }
michael@0 653
michael@0 654 nsresult
michael@0 655 nsHttpChannel::SetupTransaction()
michael@0 656 {
michael@0 657 LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
michael@0 658
michael@0 659 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
michael@0 660
michael@0 661 nsresult rv;
michael@0 662
michael@0 663 if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
michael@0 664 //
michael@0 665 // disable pipelining if:
michael@0 666 // (1) pipelining has been disabled by config
michael@0 667 // (2) pipelining has been disabled by connection mgr info
michael@0 668 // (3) request corresponds to a top-level document load (link click)
michael@0 669 // (4) request method is non-idempotent
michael@0 670 // (5) request is marked slow (e.g XHR)
michael@0 671 //
michael@0 672 if (!mAllowPipelining ||
michael@0 673 (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
michael@0 674 !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) {
michael@0 675 LOG((" pipelining disallowed\n"));
michael@0 676 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
michael@0 677 }
michael@0 678 }
michael@0 679
michael@0 680 if (!mAllowSpdy)
michael@0 681 mCaps |= NS_HTTP_DISALLOW_SPDY;
michael@0 682
michael@0 683 // Use the URI path if not proxying (transparent proxying such as proxy
michael@0 684 // CONNECT does not count here). Also figure out what HTTP version to use.
michael@0 685 nsAutoCString buf, path;
michael@0 686 nsCString* requestURI;
michael@0 687 if (mConnectionInfo->UsingConnect() ||
michael@0 688 !mConnectionInfo->UsingHttpProxy()) {
michael@0 689 rv = mURI->GetPath(path);
michael@0 690 if (NS_FAILED(rv)) return rv;
michael@0 691 // path may contain UTF-8 characters, so ensure that they're escaped.
michael@0 692 if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
michael@0 693 requestURI = &buf;
michael@0 694 else
michael@0 695 requestURI = &path;
michael@0 696 mRequestHead.SetVersion(gHttpHandler->HttpVersion());
michael@0 697 }
michael@0 698 else {
michael@0 699 rv = mURI->GetUserPass(buf);
michael@0 700 if (NS_FAILED(rv)) return rv;
michael@0 701 if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
michael@0 702 strncmp(mSpec.get(), "https:", 6) == 0)) {
michael@0 703 nsCOMPtr<nsIURI> tempURI;
michael@0 704 rv = mURI->Clone(getter_AddRefs(tempURI));
michael@0 705 if (NS_FAILED(rv)) return rv;
michael@0 706 rv = tempURI->SetUserPass(EmptyCString());
michael@0 707 if (NS_FAILED(rv)) return rv;
michael@0 708 rv = tempURI->GetAsciiSpec(path);
michael@0 709 if (NS_FAILED(rv)) return rv;
michael@0 710 requestURI = &path;
michael@0 711 }
michael@0 712 else
michael@0 713 requestURI = &mSpec;
michael@0 714 mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
michael@0 715 }
michael@0 716
michael@0 717 // trim off the #ref portion if any...
michael@0 718 int32_t ref = requestURI->FindChar('#');
michael@0 719 if (ref != kNotFound)
michael@0 720 requestURI->SetLength(ref);
michael@0 721
michael@0 722 mRequestHead.SetRequestURI(*requestURI);
michael@0 723
michael@0 724 // set the request time for cache expiration calculations
michael@0 725 mRequestTime = NowInSeconds();
michael@0 726 mRequestTimeInitialized = true;
michael@0 727
michael@0 728 // if doing a reload, force end-to-end
michael@0 729 if (mLoadFlags & LOAD_BYPASS_CACHE) {
michael@0 730 // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
michael@0 731 // no proxy is configured since we might be talking with a transparent
michael@0 732 // proxy, i.e. one that operates at the network level. See bug #14772.
michael@0 733 mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
michael@0 734 // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
michael@0 735 // no-cache'
michael@0 736 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
michael@0 737 mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
michael@0 738 }
michael@0 739 else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
michael@0 740 // We need to send 'Cache-Control: max-age=0' to force each cache along
michael@0 741 // the path to the origin server to revalidate its own entry, if any,
michael@0 742 // with the next cache or server. See bug #84847.
michael@0 743 //
michael@0 744 // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
michael@0 745 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
michael@0 746 mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
michael@0 747 else
michael@0 748 mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
michael@0 749 }
michael@0 750
michael@0 751 if (mResuming) {
michael@0 752 char byteRange[32];
michael@0 753 PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
michael@0 754 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
michael@0 755
michael@0 756 if (!mEntityID.IsEmpty()) {
michael@0 757 // Also, we want an error if this resource changed in the meantime
michael@0 758 // Format of the entity id is: escaped_etag/size/lastmod
michael@0 759 nsCString::const_iterator start, end, slash;
michael@0 760 mEntityID.BeginReading(start);
michael@0 761 mEntityID.EndReading(end);
michael@0 762 mEntityID.BeginReading(slash);
michael@0 763
michael@0 764 if (FindCharInReadable('/', slash, end)) {
michael@0 765 nsAutoCString ifMatch;
michael@0 766 mRequestHead.SetHeader(nsHttp::If_Match,
michael@0 767 NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
michael@0 768
michael@0 769 ++slash; // Incrementing, so that searching for '/' won't find
michael@0 770 // the same slash again
michael@0 771 }
michael@0 772
michael@0 773 if (FindCharInReadable('/', slash, end)) {
michael@0 774 mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
michael@0 775 Substring(++slash, end));
michael@0 776 }
michael@0 777 }
michael@0 778 }
michael@0 779
michael@0 780 // create wrapper for this channel's notification callbacks
michael@0 781 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 782 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
michael@0 783 getter_AddRefs(callbacks));
michael@0 784 if (!callbacks)
michael@0 785 return NS_ERROR_OUT_OF_MEMORY;
michael@0 786
michael@0 787 // create the transaction object
michael@0 788 mTransaction = new nsHttpTransaction();
michael@0 789 if (!mTransaction)
michael@0 790 return NS_ERROR_OUT_OF_MEMORY;
michael@0 791
michael@0 792 // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
michael@0 793 if (mLoadFlags & LOAD_ANONYMOUS)
michael@0 794 mCaps |= NS_HTTP_LOAD_ANONYMOUS;
michael@0 795
michael@0 796 if (mTimingEnabled)
michael@0 797 mCaps |= NS_HTTP_TIMING_ENABLED;
michael@0 798
michael@0 799 if (mUpgradeProtocolCallback) {
michael@0 800 mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
michael@0 801 mRequestHead.SetHeaderOnce(nsHttp::Connection,
michael@0 802 nsHttp::Upgrade.get(),
michael@0 803 true);
michael@0 804 mCaps |= NS_HTTP_STICKY_CONNECTION;
michael@0 805 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
michael@0 806 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
michael@0 807 mCaps |= NS_HTTP_DISALLOW_SPDY;
michael@0 808 }
michael@0 809
michael@0 810 nsCOMPtr<nsIAsyncInputStream> responseStream;
michael@0 811 rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
michael@0 812 mUploadStream, mUploadStreamHasHeaders,
michael@0 813 NS_GetCurrentThread(), callbacks, this,
michael@0 814 getter_AddRefs(responseStream));
michael@0 815 if (NS_FAILED(rv)) {
michael@0 816 mTransaction = nullptr;
michael@0 817 return rv;
michael@0 818 }
michael@0 819
michael@0 820 SetupTransactionLoadGroupInfo();
michael@0 821
michael@0 822 rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
michael@0 823 responseStream);
michael@0 824 return rv;
michael@0 825 }
michael@0 826
michael@0 827 // NOTE: This function duplicates code from nsBaseChannel. This will go away
michael@0 828 // once HTTP uses nsBaseChannel (part of bug 312760)
michael@0 829 static void
michael@0 830 CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
michael@0 831 {
michael@0 832 nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
michael@0 833
michael@0 834 nsAutoCString newType;
michael@0 835 NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
michael@0 836 if (!newType.IsEmpty()) {
michael@0 837 chan->SetContentType(newType);
michael@0 838 }
michael@0 839 }
michael@0 840
michael@0 841 nsresult
michael@0 842 nsHttpChannel::CallOnStartRequest()
michael@0 843 {
michael@0 844 nsresult rv;
michael@0 845
michael@0 846 mTracingEnabled = false;
michael@0 847
michael@0 848 // Allow consumers to override our content type
michael@0 849 if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
michael@0 850 // NOTE: We can have both a txn pump and a cache pump when the cache
michael@0 851 // content is partial. In that case, we need to read from the cache,
michael@0 852 // because that's the one that has the initial contents. If that fails
michael@0 853 // then give the transaction pump a shot.
michael@0 854
michael@0 855 nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
michael@0 856
michael@0 857 bool typeSniffersCalled = false;
michael@0 858 if (mCachePump) {
michael@0 859 typeSniffersCalled =
michael@0 860 NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
michael@0 861 }
michael@0 862
michael@0 863 if (!typeSniffersCalled && mTransactionPump) {
michael@0 864 mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
michael@0 865 }
michael@0 866 }
michael@0 867
michael@0 868 bool shouldSniff = mResponseHead && (mResponseHead->ContentType().IsEmpty() ||
michael@0 869 ((mResponseHead->ContentType().EqualsLiteral(APPLICATION_OCTET_STREAM) &&
michael@0 870 (mLoadFlags & LOAD_TREAT_APPLICATION_OCTET_STREAM_AS_UNKNOWN))));
michael@0 871
michael@0 872 if (shouldSniff) {
michael@0 873 MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
michael@0 874 if (!mContentTypeHint.IsEmpty())
michael@0 875 mResponseHead->SetContentType(mContentTypeHint);
michael@0 876 else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 &&
michael@0 877 mConnectionInfo->Port() != mConnectionInfo->DefaultPort())
michael@0 878 mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
michael@0 879 else {
michael@0 880 // Uh-oh. We had better find out what type we are!
michael@0 881
michael@0 882 // XXX This does not work with content-encodings... but
michael@0 883 // neither does applying the conversion from the URILoader
michael@0 884
michael@0 885 nsCOMPtr<nsIStreamConverterService> serv;
michael@0 886 rv = gHttpHandler->
michael@0 887 GetStreamConverterService(getter_AddRefs(serv));
michael@0 888 // If we failed, we just fall through to the "normal" case
michael@0 889 if (NS_SUCCEEDED(rv)) {
michael@0 890 nsCOMPtr<nsIStreamListener> converter;
michael@0 891 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
michael@0 892 "*/*",
michael@0 893 mListener,
michael@0 894 mListenerContext,
michael@0 895 getter_AddRefs(converter));
michael@0 896 if (NS_SUCCEEDED(rv)) {
michael@0 897 mListener = converter;
michael@0 898 }
michael@0 899 }
michael@0 900 }
michael@0 901 }
michael@0 902
michael@0 903 if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
michael@0 904 mResponseHead->SetContentCharset(mContentCharsetHint);
michael@0 905
michael@0 906 if (mResponseHead && mCacheEntry) {
michael@0 907 // If we have a cache entry, set its predicted size to ContentLength to
michael@0 908 // avoid caching an entry that will exceed the max size limit.
michael@0 909 rv = mCacheEntry->SetPredictedDataSize(
michael@0 910 mResponseHead->ContentLength());
michael@0 911 if (NS_ERROR_FILE_TOO_BIG == rv) {
michael@0 912 mCacheEntry = nullptr;
michael@0 913 LOG((" entry too big, throwing away"));
michael@0 914 } else {
michael@0 915 NS_ENSURE_SUCCESS(rv, rv);
michael@0 916 }
michael@0 917 }
michael@0 918
michael@0 919 LOG((" calling mListener->OnStartRequest\n"));
michael@0 920 if (mListener) {
michael@0 921 rv = mListener->OnStartRequest(this, mListenerContext);
michael@0 922 if (NS_FAILED(rv))
michael@0 923 return rv;
michael@0 924 } else {
michael@0 925 NS_WARNING("OnStartRequest skipped because of null listener");
michael@0 926 }
michael@0 927
michael@0 928 // install stream converter if required
michael@0 929 rv = ApplyContentConversions();
michael@0 930 if (NS_FAILED(rv)) return rv;
michael@0 931
michael@0 932 rv = EnsureAssocReq();
michael@0 933 if (NS_FAILED(rv))
michael@0 934 return rv;
michael@0 935
michael@0 936 // if this channel is for a download, close off access to the cache.
michael@0 937 if (mCacheEntry && mChannelIsForDownload) {
michael@0 938 mCacheEntry->AsyncDoom(nullptr);
michael@0 939
michael@0 940 // We must keep the cache entry in case of partial request.
michael@0 941 // Concurrent access is the same, we need the entry in
michael@0 942 // OnStopRequest.
michael@0 943 if (!mCachedContentIsPartial && !mConcurentCacheAccess)
michael@0 944 CloseCacheEntry(false);
michael@0 945 }
michael@0 946
michael@0 947 if (!mCanceled) {
michael@0 948 // create offline cache entry if offline caching was requested
michael@0 949 if (ShouldUpdateOfflineCacheEntry()) {
michael@0 950 LOG(("writing to the offline cache"));
michael@0 951 rv = InitOfflineCacheEntry();
michael@0 952 if (NS_FAILED(rv)) return rv;
michael@0 953
michael@0 954 // InitOfflineCacheEntry may have closed mOfflineCacheEntry
michael@0 955 if (mOfflineCacheEntry) {
michael@0 956 rv = InstallOfflineCacheListener();
michael@0 957 if (NS_FAILED(rv)) return rv;
michael@0 958 }
michael@0 959 } else if (mApplicationCacheForWrite) {
michael@0 960 LOG(("offline cache is up to date, not updating"));
michael@0 961 CloseOfflineCacheEntry();
michael@0 962 }
michael@0 963 }
michael@0 964
michael@0 965 return NS_OK;
michael@0 966 }
michael@0 967
michael@0 968 nsresult
michael@0 969 nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus)
michael@0 970 {
michael@0 971 // Failure to set up a proxy tunnel via CONNECT means one of the following:
michael@0 972 // 1) Proxy wants authorization, or forbids.
michael@0 973 // 2) DNS at proxy couldn't resolve target URL.
michael@0 974 // 3) Proxy connection to target failed or timed out.
michael@0 975 // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
michael@0 976 //
michael@0 977 // Our current architecture would parse the proxy's response content with
michael@0 978 // the permission of the target URL. Given #4, we must avoid rendering the
michael@0 979 // body of the reply, and instead give the user a (hopefully helpful)
michael@0 980 // boilerplate error page, based on just the HTTP status of the reply.
michael@0 981
michael@0 982 MOZ_ASSERT(mConnectionInfo->UsingConnect(),
michael@0 983 "proxy connect failed but not using CONNECT?");
michael@0 984 nsresult rv;
michael@0 985 switch (httpStatus)
michael@0 986 {
michael@0 987 case 300: case 301: case 302: case 303: case 307: case 308:
michael@0 988 // Bad redirect: not top-level, or it's a POST, bad/missing Location,
michael@0 989 // or ProcessRedirect() failed for some other reason. Legal
michael@0 990 // redirects that fail because site not available, etc., are handled
michael@0 991 // elsewhere, in the regular codepath.
michael@0 992 rv = NS_ERROR_CONNECTION_REFUSED;
michael@0 993 break;
michael@0 994 case 403: // HTTP/1.1: "Forbidden"
michael@0 995 case 407: // ProcessAuthentication() failed
michael@0 996 case 501: // HTTP/1.1: "Not Implemented"
michael@0 997 // user sees boilerplate Mozilla "Proxy Refused Connection" page.
michael@0 998 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
michael@0 999 break;
michael@0 1000 // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
michael@0 1001 case 404: // HTTP/1.1: "Not Found"
michael@0 1002 // RFC 2616: "some deployed proxies are known to return 400 or 500 when
michael@0 1003 // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
michael@0 1004 // we have a conflict here).
michael@0 1005 case 400: // HTTP/1.1 "Bad Request"
michael@0 1006 case 500: // HTTP/1.1: "Internal Server Error"
michael@0 1007 /* User sees: "Address Not Found: Firefox can't find the server at
michael@0 1008 * www.foo.com."
michael@0 1009 */
michael@0 1010 rv = NS_ERROR_UNKNOWN_HOST;
michael@0 1011 break;
michael@0 1012 case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
michael@0 1013 // Squid returns 503 if target request fails for anything but DNS.
michael@0 1014 case 503: // HTTP/1.1: "Service Unavailable"
michael@0 1015 /* User sees: "Failed to Connect:
michael@0 1016 * Firefox can't establish a connection to the server at
michael@0 1017 * www.foo.com. Though the site seems valid, the browser
michael@0 1018 * was unable to establish a connection."
michael@0 1019 */
michael@0 1020 rv = NS_ERROR_CONNECTION_REFUSED;
michael@0 1021 break;
michael@0 1022 // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
michael@0 1023 // do here: picking target timeout, as DNS covered by 400/404/500
michael@0 1024 case 504: // HTTP/1.1: "Gateway Timeout"
michael@0 1025 // user sees: "Network Timeout: The server at www.foo.com
michael@0 1026 // is taking too long to respond."
michael@0 1027 rv = NS_ERROR_NET_TIMEOUT;
michael@0 1028 break;
michael@0 1029 // Confused proxy server or malicious response
michael@0 1030 default:
michael@0 1031 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
michael@0 1032 break;
michael@0 1033 }
michael@0 1034 LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
michael@0 1035 this, httpStatus));
michael@0 1036 Cancel(rv);
michael@0 1037 CallOnStartRequest();
michael@0 1038 return rv;
michael@0 1039 }
michael@0 1040
michael@0 1041 /**
michael@0 1042 * Decide whether or not to remember Strict-Transport-Security, and whether
michael@0 1043 * or not to enforce channel integrity.
michael@0 1044 *
michael@0 1045 * @return NS_ERROR_FAILURE if there's security information missing even though
michael@0 1046 * it's an HTTPS connection.
michael@0 1047 */
michael@0 1048 nsresult
michael@0 1049 nsHttpChannel::ProcessSTSHeader()
michael@0 1050 {
michael@0 1051 nsresult rv;
michael@0 1052 bool isHttps = false;
michael@0 1053 rv = mURI->SchemeIs("https", &isHttps);
michael@0 1054 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1055
michael@0 1056 // If this channel is not loading securely, STS doesn't do anything.
michael@0 1057 // The upgrade to HTTPS takes place earlier in the channel load process.
michael@0 1058 if (!isHttps)
michael@0 1059 return NS_OK;
michael@0 1060
michael@0 1061 nsAutoCString asciiHost;
michael@0 1062 rv = mURI->GetAsciiHost(asciiHost);
michael@0 1063 NS_ENSURE_SUCCESS(rv, NS_OK);
michael@0 1064
michael@0 1065 // If the channel is not a hostname, but rather an IP, STS doesn't do
michael@0 1066 // anything.
michael@0 1067 PRNetAddr hostAddr;
michael@0 1068 if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
michael@0 1069 return NS_OK;
michael@0 1070
michael@0 1071 nsISiteSecurityService* sss = gHttpHandler->GetSSService();
michael@0 1072 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
michael@0 1073
michael@0 1074 // mSecurityInfo may not always be present, and if it's not then it is okay
michael@0 1075 // to just disregard any STS headers since we know nothing about the
michael@0 1076 // security of the connection.
michael@0 1077 NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
michael@0 1078
michael@0 1079 // Check the trustworthiness of the channel (are there any cert errors?)
michael@0 1080 // If there are certificate errors, we still load the data, we just ignore
michael@0 1081 // any STS headers that are present.
michael@0 1082 bool tlsIsBroken = false;
michael@0 1083 rv = sss->ShouldIgnoreHeaders(mSecurityInfo, &tlsIsBroken);
michael@0 1084 NS_ENSURE_SUCCESS(rv, NS_OK);
michael@0 1085
michael@0 1086 // If this was already an STS host, the connection should have been aborted
michael@0 1087 // by the bad cert handler in the case of cert errors. If it didn't abort the connection,
michael@0 1088 // there's probably something funny going on.
michael@0 1089 // If this wasn't an STS host, errors are allowed, but no more STS processing
michael@0 1090 // will happen during the session.
michael@0 1091 bool wasAlreadySTSHost;
michael@0 1092 uint32_t flags =
michael@0 1093 NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
michael@0 1094 rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
michael@0 1095 &wasAlreadySTSHost);
michael@0 1096 // Failure here means STS is broken. Don't prevent the load, but this
michael@0 1097 // shouldn't fail.
michael@0 1098 NS_ENSURE_SUCCESS(rv, NS_OK);
michael@0 1099 MOZ_ASSERT(!(wasAlreadySTSHost && tlsIsBroken),
michael@0 1100 "connection should have been aborted by nss-bad-cert-handler");
michael@0 1101
michael@0 1102 // Any STS header is ignored if the channel is not trusted due to
michael@0 1103 // certificate errors (STS Spec 7.1) -- there is nothing else to do, and
michael@0 1104 // the load may progress.
michael@0 1105 if (tlsIsBroken) {
michael@0 1106 LOG(("STS: Transport layer is not trustworthy, ignoring "
michael@0 1107 "STS headers and continuing load\n"));
michael@0 1108 return NS_OK;
michael@0 1109 }
michael@0 1110
michael@0 1111 // If there's a STS header, process it (STS Spec 7.1). At this point in
michael@0 1112 // processing, the channel is trusted, so the header should not be ignored.
michael@0 1113 const nsHttpAtom atom = nsHttp::ResolveAtom("Strict-Transport-Security");
michael@0 1114 nsAutoCString stsHeader;
michael@0 1115 rv = mResponseHead->GetHeader(atom, stsHeader);
michael@0 1116 if (rv == NS_ERROR_NOT_AVAILABLE) {
michael@0 1117 LOG(("STS: No STS header, continuing load.\n"));
michael@0 1118 return NS_OK;
michael@0 1119 }
michael@0 1120 // All other failures are fatal.
michael@0 1121 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1122
michael@0 1123 rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, mURI,
michael@0 1124 stsHeader.get(), flags, nullptr, nullptr);
michael@0 1125 if (NS_FAILED(rv)) {
michael@0 1126 AddSecurityMessage(NS_LITERAL_STRING("InvalidSTSHeaders"),
michael@0 1127 NS_LITERAL_STRING("Invalid HSTS Headers"));
michael@0 1128 LOG(("STS: Failed to parse STS header, continuing load.\n"));
michael@0 1129 }
michael@0 1130
michael@0 1131 return NS_OK;
michael@0 1132 }
michael@0 1133
michael@0 1134 bool
michael@0 1135 nsHttpChannel::IsHTTPS()
michael@0 1136 {
michael@0 1137 bool isHttps;
michael@0 1138 if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
michael@0 1139 return false;
michael@0 1140 return true;
michael@0 1141 }
michael@0 1142
michael@0 1143 void
michael@0 1144 nsHttpChannel::ProcessSSLInformation()
michael@0 1145 {
michael@0 1146 // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
michael@0 1147 // can be whitelisted for TLS False Start in future sessions. We could
michael@0 1148 // do the same for DH but its rarity doesn't justify the lookup.
michael@0 1149
michael@0 1150 if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo ||
michael@0 1151 !IsHTTPS() || mPrivateBrowsing)
michael@0 1152 return;
michael@0 1153
michael@0 1154 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo);
michael@0 1155 nsCOMPtr<nsISSLStatusProvider> statusProvider =
michael@0 1156 do_QueryInterface(mSecurityInfo);
michael@0 1157 if (!ssl || !statusProvider)
michael@0 1158 return;
michael@0 1159 nsCOMPtr<nsISSLStatus> sslstat;
michael@0 1160 statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
michael@0 1161 if (!sslstat)
michael@0 1162 return;
michael@0 1163
michael@0 1164 // If certificate exceptions are being used don't record this information
michael@0 1165 // in the permission manager.
michael@0 1166 bool trustCheck;
michael@0 1167 if (NS_FAILED(sslstat->GetIsDomainMismatch(&trustCheck)) || trustCheck)
michael@0 1168 return;
michael@0 1169 if (NS_FAILED(sslstat->GetIsNotValidAtThisTime(&trustCheck)) || trustCheck)
michael@0 1170 return;
michael@0 1171 if (NS_FAILED(sslstat->GetIsUntrusted(&trustCheck)) || trustCheck)
michael@0 1172 return;
michael@0 1173
michael@0 1174 int16_t kea = ssl->GetKEAUsed();
michael@0 1175
michael@0 1176 nsIPrincipal *principal = GetPrincipal();
michael@0 1177 if (!principal)
michael@0 1178 return;
michael@0 1179
michael@0 1180 // set a permission manager flag that future transactions can
michael@0 1181 // use via RetrieveSSLOptions(()
michael@0 1182
michael@0 1183 nsCOMPtr<nsIPermissionManager> permMgr =
michael@0 1184 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
michael@0 1185 if (!permMgr)
michael@0 1186 return;
michael@0 1187
michael@0 1188 // Allow this to stand for a week
michael@0 1189 int64_t expireTime = (PR_Now() / PR_USEC_PER_MSEC) +
michael@0 1190 (86400 * 7 * PR_MSEC_PER_SEC);
michael@0 1191
michael@0 1192 if (kea == ssl_kea_rsa) {
michael@0 1193 permMgr->AddFromPrincipal(principal, "falsestart-rsa",
michael@0 1194 nsIPermissionManager::ALLOW_ACTION,
michael@0 1195 nsIPermissionManager::EXPIRE_TIME,
michael@0 1196 expireTime);
michael@0 1197 LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] "
michael@0 1198 "falsestart-rsa permission granted for this host\n", this));
michael@0 1199 } else {
michael@0 1200 permMgr->RemoveFromPrincipal(principal, "falsestart-rsa");
michael@0 1201 }
michael@0 1202 }
michael@0 1203
michael@0 1204 nsresult
michael@0 1205 nsHttpChannel::ProcessResponse()
michael@0 1206 {
michael@0 1207 nsresult rv;
michael@0 1208 uint32_t httpStatus = mResponseHead->Status();
michael@0 1209
michael@0 1210 // Gather data on whether the transaction and page (if this is
michael@0 1211 // the initial page load) is being loaded with SSL.
michael@0 1212 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
michael@0 1213 mConnectionInfo->UsingSSL());
michael@0 1214 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
michael@0 1215 Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
michael@0 1216 mConnectionInfo->UsingSSL());
michael@0 1217 }
michael@0 1218
michael@0 1219 LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
michael@0 1220 this, httpStatus));
michael@0 1221
michael@0 1222 if (mTransaction->ProxyConnectFailed()) {
michael@0 1223 // Only allow 407 (authentication required) to continue
michael@0 1224 if (httpStatus != 407)
michael@0 1225 return ProcessFailedProxyConnect(httpStatus);
michael@0 1226 // If proxy CONNECT response needs to complete, wait to process connection
michael@0 1227 // for Strict-Transport-Security.
michael@0 1228 } else {
michael@0 1229 // Given a successful connection, process any STS data that's relevant.
michael@0 1230 rv = ProcessSTSHeader();
michael@0 1231 MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
michael@0 1232 }
michael@0 1233
michael@0 1234 MOZ_ASSERT(!mCachedContentIsValid);
michael@0 1235
michael@0 1236 ProcessSSLInformation();
michael@0 1237
michael@0 1238 // notify "http-on-examine-response" observers
michael@0 1239 gHttpHandler->OnExamineResponse(this);
michael@0 1240
michael@0 1241 SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
michael@0 1242
michael@0 1243 // handle unused username and password in url (see bug 232567)
michael@0 1244 if (httpStatus != 401 && httpStatus != 407) {
michael@0 1245 if (!mAuthRetryPending)
michael@0 1246 mAuthProvider->CheckForSuperfluousAuth();
michael@0 1247 if (mCanceled)
michael@0 1248 return CallOnStartRequest();
michael@0 1249
michael@0 1250 // reset the authentication's current continuation state because our
michael@0 1251 // last authentication attempt has been completed successfully
michael@0 1252 mAuthProvider->Disconnect(NS_ERROR_ABORT);
michael@0 1253 mAuthProvider = nullptr;
michael@0 1254 LOG((" continuation state has been reset"));
michael@0 1255 }
michael@0 1256
michael@0 1257 bool successfulReval = false;
michael@0 1258
michael@0 1259 // handle different server response categories. Note that we handle
michael@0 1260 // caching or not caching of error pages in
michael@0 1261 // nsHttpResponseHead::MustValidate; if you change this switch, update that
michael@0 1262 // one
michael@0 1263 switch (httpStatus) {
michael@0 1264 case 200:
michael@0 1265 case 203:
michael@0 1266 // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
michael@0 1267 // So if a server does that and sends 200 instead of 206 that we
michael@0 1268 // expect, notify our caller.
michael@0 1269 // However, if we wanted to start from the beginning, let it go through
michael@0 1270 if (mResuming && mStartPos != 0) {
michael@0 1271 LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
michael@0 1272 Cancel(NS_ERROR_NOT_RESUMABLE);
michael@0 1273 rv = CallOnStartRequest();
michael@0 1274 break;
michael@0 1275 }
michael@0 1276 // these can normally be cached
michael@0 1277 rv = ProcessNormal();
michael@0 1278 MaybeInvalidateCacheEntryForSubsequentGet();
michael@0 1279 break;
michael@0 1280 case 206:
michael@0 1281 if (mCachedContentIsPartial) // an internal byte range request...
michael@0 1282 rv = ProcessPartialContent();
michael@0 1283 else {
michael@0 1284 mCacheInputStream.CloseAndRelease();
michael@0 1285 rv = ProcessNormal();
michael@0 1286 }
michael@0 1287 break;
michael@0 1288 case 300:
michael@0 1289 case 301:
michael@0 1290 case 302:
michael@0 1291 case 307:
michael@0 1292 case 308:
michael@0 1293 case 303:
michael@0 1294 #if 0
michael@0 1295 case 305: // disabled as a security measure (see bug 187996).
michael@0 1296 #endif
michael@0 1297 // don't store the response body for redirects
michael@0 1298 MaybeInvalidateCacheEntryForSubsequentGet();
michael@0 1299 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
michael@0 1300 rv = AsyncProcessRedirection(httpStatus);
michael@0 1301 if (NS_FAILED(rv)) {
michael@0 1302 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
michael@0 1303 LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
michael@0 1304 // don't cache failed redirect responses.
michael@0 1305 if (mCacheEntry)
michael@0 1306 mCacheEntry->AsyncDoom(nullptr);
michael@0 1307 if (DoNotRender3xxBody(rv)) {
michael@0 1308 mStatus = rv;
michael@0 1309 DoNotifyListener();
michael@0 1310 } else {
michael@0 1311 rv = ContinueProcessResponse(rv);
michael@0 1312 }
michael@0 1313 }
michael@0 1314 break;
michael@0 1315 case 304:
michael@0 1316 rv = ProcessNotModified();
michael@0 1317 if (NS_FAILED(rv)) {
michael@0 1318 LOG(("ProcessNotModified failed [rv=%x]\n", rv));
michael@0 1319 mCacheInputStream.CloseAndRelease();
michael@0 1320 rv = ProcessNormal();
michael@0 1321 }
michael@0 1322 else {
michael@0 1323 successfulReval = true;
michael@0 1324 }
michael@0 1325 break;
michael@0 1326 case 401:
michael@0 1327 case 407:
michael@0 1328 rv = mAuthProvider->ProcessAuthentication(
michael@0 1329 httpStatus, mConnectionInfo->UsingSSL() &&
michael@0 1330 mTransaction->ProxyConnectFailed());
michael@0 1331 if (rv == NS_ERROR_IN_PROGRESS) {
michael@0 1332 // authentication prompt has been invoked and result
michael@0 1333 // is expected asynchronously
michael@0 1334 mAuthRetryPending = true;
michael@0 1335 if (httpStatus == 407 || mTransaction->ProxyConnectFailed())
michael@0 1336 mProxyAuthPending = true;
michael@0 1337
michael@0 1338 // suspend the transaction pump to stop receiving the
michael@0 1339 // unauthenticated content data. We will throw that data
michael@0 1340 // away when user provides credentials or resume the pump
michael@0 1341 // when user refuses to authenticate.
michael@0 1342 LOG(("Suspending the transaction, asynchronously prompting for credentials"));
michael@0 1343 mTransactionPump->Suspend();
michael@0 1344 rv = NS_OK;
michael@0 1345 }
michael@0 1346 else if (NS_FAILED(rv)) {
michael@0 1347 LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
michael@0 1348 if (mTransaction->ProxyConnectFailed())
michael@0 1349 return ProcessFailedProxyConnect(httpStatus);
michael@0 1350 if (!mAuthRetryPending)
michael@0 1351 mAuthProvider->CheckForSuperfluousAuth();
michael@0 1352 rv = ProcessNormal();
michael@0 1353 }
michael@0 1354 else
michael@0 1355 mAuthRetryPending = true; // see DoAuthRetry
michael@0 1356 break;
michael@0 1357 default:
michael@0 1358 rv = ProcessNormal();
michael@0 1359 MaybeInvalidateCacheEntryForSubsequentGet();
michael@0 1360 break;
michael@0 1361 }
michael@0 1362
michael@0 1363 CacheDisposition cacheDisposition;
michael@0 1364 if (!mDidReval)
michael@0 1365 cacheDisposition = kCacheMissed;
michael@0 1366 else if (successfulReval)
michael@0 1367 cacheDisposition = kCacheHitViaReval;
michael@0 1368 else
michael@0 1369 cacheDisposition = kCacheMissedViaReval;
michael@0 1370
michael@0 1371 AccumulateCacheHitTelemetry(cacheDisposition);
michael@0 1372
michael@0 1373 return rv;
michael@0 1374 }
michael@0 1375
michael@0 1376 nsresult
michael@0 1377 nsHttpChannel::ContinueProcessResponse(nsresult rv)
michael@0 1378 {
michael@0 1379 bool doNotRender = DoNotRender3xxBody(rv);
michael@0 1380
michael@0 1381 if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
michael@0 1382 bool isHTTP = false;
michael@0 1383 if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
michael@0 1384 isHTTP = false;
michael@0 1385 if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
michael@0 1386 isHTTP = false;
michael@0 1387
michael@0 1388 if (!isHTTP) {
michael@0 1389 // This was a blocked attempt to redirect and subvert the system by
michael@0 1390 // redirecting to another protocol (perhaps javascript:)
michael@0 1391 // In that case we want to throw an error instead of displaying the
michael@0 1392 // non-redirected response body.
michael@0 1393 LOG(("ContinueProcessResponse detected rejected Non-HTTP Redirection"));
michael@0 1394 doNotRender = true;
michael@0 1395 rv = NS_ERROR_CORRUPTED_CONTENT;
michael@0 1396 }
michael@0 1397 }
michael@0 1398
michael@0 1399 if (doNotRender) {
michael@0 1400 Cancel(rv);
michael@0 1401 DoNotifyListener();
michael@0 1402 return rv;
michael@0 1403 }
michael@0 1404
michael@0 1405 if (NS_SUCCEEDED(rv)) {
michael@0 1406 UpdateInhibitPersistentCachingFlag();
michael@0 1407
michael@0 1408 InitCacheEntry();
michael@0 1409 CloseCacheEntry(false);
michael@0 1410
michael@0 1411 if (mApplicationCacheForWrite) {
michael@0 1412 // Store response in the offline cache
michael@0 1413 InitOfflineCacheEntry();
michael@0 1414 CloseOfflineCacheEntry();
michael@0 1415 }
michael@0 1416 return NS_OK;
michael@0 1417 }
michael@0 1418
michael@0 1419 LOG(("ContinueProcessResponse got failure result [rv=%x]\n", rv));
michael@0 1420 if (mTransaction->ProxyConnectFailed()) {
michael@0 1421 return ProcessFailedProxyConnect(mRedirectType);
michael@0 1422 }
michael@0 1423 return ProcessNormal();
michael@0 1424 }
michael@0 1425
michael@0 1426 nsresult
michael@0 1427 nsHttpChannel::ProcessNormal()
michael@0 1428 {
michael@0 1429 nsresult rv;
michael@0 1430
michael@0 1431 LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
michael@0 1432
michael@0 1433 bool succeeded;
michael@0 1434 rv = GetRequestSucceeded(&succeeded);
michael@0 1435 if (NS_SUCCEEDED(rv) && !succeeded) {
michael@0 1436 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
michael@0 1437 bool waitingForRedirectCallback;
michael@0 1438 (void)ProcessFallback(&waitingForRedirectCallback);
michael@0 1439 if (waitingForRedirectCallback) {
michael@0 1440 // The transaction has been suspended by ProcessFallback.
michael@0 1441 return NS_OK;
michael@0 1442 }
michael@0 1443 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
michael@0 1444 }
michael@0 1445
michael@0 1446 return ContinueProcessNormal(NS_OK);
michael@0 1447 }
michael@0 1448
michael@0 1449 nsresult
michael@0 1450 nsHttpChannel::ContinueProcessNormal(nsresult rv)
michael@0 1451 {
michael@0 1452 if (NS_FAILED(rv)) {
michael@0 1453 // Fill the failure status here, we have failed to fall back, thus we
michael@0 1454 // have to report our status as failed.
michael@0 1455 mStatus = rv;
michael@0 1456 DoNotifyListener();
michael@0 1457 return rv;
michael@0 1458 }
michael@0 1459
michael@0 1460 if (mFallingBack) {
michael@0 1461 // Do not continue with normal processing, fallback is in
michael@0 1462 // progress now.
michael@0 1463 return NS_OK;
michael@0 1464 }
michael@0 1465
michael@0 1466 // if we're here, then any byte-range requests failed to result in a partial
michael@0 1467 // response. we must clear this flag to prevent BufferPartialContent from
michael@0 1468 // being called inside our OnDataAvailable (see bug 136678).
michael@0 1469 mCachedContentIsPartial = false;
michael@0 1470
michael@0 1471 ClearBogusContentEncodingIfNeeded();
michael@0 1472
michael@0 1473 UpdateInhibitPersistentCachingFlag();
michael@0 1474
michael@0 1475 // this must be called before firing OnStartRequest, since http clients,
michael@0 1476 // such as imagelib, expect our cache entry to already have the correct
michael@0 1477 // expiration time (bug 87710).
michael@0 1478 if (mCacheEntry) {
michael@0 1479 rv = InitCacheEntry();
michael@0 1480 if (NS_FAILED(rv))
michael@0 1481 CloseCacheEntry(true);
michael@0 1482 }
michael@0 1483
michael@0 1484 // Check that the server sent us what we were asking for
michael@0 1485 if (mResuming) {
michael@0 1486 // Create an entity id from the response
michael@0 1487 nsAutoCString id;
michael@0 1488 rv = GetEntityID(id);
michael@0 1489 if (NS_FAILED(rv)) {
michael@0 1490 // If creating an entity id is not possible -> error
michael@0 1491 Cancel(NS_ERROR_NOT_RESUMABLE);
michael@0 1492 }
michael@0 1493 else if (mResponseHead->Status() != 206 &&
michael@0 1494 mResponseHead->Status() != 200) {
michael@0 1495 // Probably 404 Not Found, 412 Precondition Failed or
michael@0 1496 // 416 Invalid Range -> error
michael@0 1497 LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
michael@0 1498 this));
michael@0 1499 Cancel(NS_ERROR_ENTITY_CHANGED);
michael@0 1500 }
michael@0 1501 // If we were passed an entity id, verify it's equal to the server's
michael@0 1502 else if (!mEntityID.IsEmpty()) {
michael@0 1503 if (!mEntityID.Equals(id)) {
michael@0 1504 LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
michael@0 1505 mEntityID.get(), id.get(), this));
michael@0 1506 Cancel(NS_ERROR_ENTITY_CHANGED);
michael@0 1507 }
michael@0 1508 }
michael@0 1509 }
michael@0 1510
michael@0 1511 rv = CallOnStartRequest();
michael@0 1512 if (NS_FAILED(rv)) return rv;
michael@0 1513
michael@0 1514 // install cache listener if we still have a cache entry open
michael@0 1515 if (mCacheEntry && !mLoadedFromApplicationCache) {
michael@0 1516 rv = InstallCacheListener();
michael@0 1517 if (NS_FAILED(rv)) return rv;
michael@0 1518 }
michael@0 1519
michael@0 1520 return NS_OK;
michael@0 1521 }
michael@0 1522
michael@0 1523 nsresult
michael@0 1524 nsHttpChannel::PromptTempRedirect()
michael@0 1525 {
michael@0 1526 if (!gHttpHandler->PromptTempRedirect()) {
michael@0 1527 return NS_OK;
michael@0 1528 }
michael@0 1529 nsresult rv;
michael@0 1530 nsCOMPtr<nsIStringBundleService> bundleService =
michael@0 1531 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
michael@0 1532 if (NS_FAILED(rv)) return rv;
michael@0 1533
michael@0 1534 nsCOMPtr<nsIStringBundle> stringBundle;
michael@0 1535 rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
michael@0 1536 if (NS_FAILED(rv)) return rv;
michael@0 1537
michael@0 1538 nsXPIDLString messageString;
michael@0 1539 rv = stringBundle->GetStringFromName(MOZ_UTF16("RepostFormData"), getter_Copies(messageString));
michael@0 1540 // GetStringFromName can return NS_OK and nullptr messageString.
michael@0 1541 if (NS_SUCCEEDED(rv) && messageString) {
michael@0 1542 bool repost = false;
michael@0 1543
michael@0 1544 nsCOMPtr<nsIPrompt> prompt;
michael@0 1545 GetCallback(prompt);
michael@0 1546 if (!prompt)
michael@0 1547 return NS_ERROR_NO_INTERFACE;
michael@0 1548
michael@0 1549 prompt->Confirm(nullptr, messageString, &repost);
michael@0 1550 if (!repost)
michael@0 1551 return NS_ERROR_FAILURE;
michael@0 1552 }
michael@0 1553
michael@0 1554 return rv;
michael@0 1555 }
michael@0 1556
michael@0 1557 nsresult
michael@0 1558 nsHttpChannel::ProxyFailover()
michael@0 1559 {
michael@0 1560 LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
michael@0 1561
michael@0 1562 nsresult rv;
michael@0 1563
michael@0 1564 nsCOMPtr<nsIProtocolProxyService> pps =
michael@0 1565 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
michael@0 1566 if (NS_FAILED(rv))
michael@0 1567 return rv;
michael@0 1568
michael@0 1569 nsCOMPtr<nsIProxyInfo> pi;
michael@0 1570 rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
michael@0 1571 getter_AddRefs(pi));
michael@0 1572 if (NS_FAILED(rv))
michael@0 1573 return rv;
michael@0 1574
michael@0 1575 // XXXbz so where does this codepath remove us from the loadgroup,
michael@0 1576 // exactly?
michael@0 1577 return AsyncDoReplaceWithProxy(pi);
michael@0 1578 }
michael@0 1579
michael@0 1580 void
michael@0 1581 nsHttpChannel::HandleAsyncRedirectChannelToHttps()
michael@0 1582 {
michael@0 1583 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
michael@0 1584
michael@0 1585 if (mSuspendCount) {
michael@0 1586 LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
michael@0 1587 mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
michael@0 1588 return;
michael@0 1589 }
michael@0 1590
michael@0 1591 nsresult rv = StartRedirectChannelToHttps();
michael@0 1592 if (NS_FAILED(rv))
michael@0 1593 ContinueAsyncRedirectChannelToURI(rv);
michael@0 1594 }
michael@0 1595
michael@0 1596 nsresult
michael@0 1597 nsHttpChannel::StartRedirectChannelToHttps()
michael@0 1598 {
michael@0 1599 nsresult rv = NS_OK;
michael@0 1600 LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
michael@0 1601
michael@0 1602 nsCOMPtr<nsIURI> upgradedURI;
michael@0 1603
michael@0 1604 rv = mURI->Clone(getter_AddRefs(upgradedURI));
michael@0 1605 NS_ENSURE_SUCCESS(rv,rv);
michael@0 1606
michael@0 1607 upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
michael@0 1608
michael@0 1609 int32_t oldPort = -1;
michael@0 1610 rv = mURI->GetPort(&oldPort);
michael@0 1611 if (NS_FAILED(rv)) return rv;
michael@0 1612
michael@0 1613 // Keep any nonstandard ports so only the scheme is changed.
michael@0 1614 // For example:
michael@0 1615 // http://foo.com:80 -> https://foo.com:443
michael@0 1616 // http://foo.com:81 -> https://foo.com:81
michael@0 1617
michael@0 1618 if (oldPort == 80 || oldPort == -1)
michael@0 1619 upgradedURI->SetPort(-1);
michael@0 1620 else
michael@0 1621 upgradedURI->SetPort(oldPort);
michael@0 1622
michael@0 1623 return StartRedirectChannelToURI(upgradedURI,
michael@0 1624 nsIChannelEventSink::REDIRECT_PERMANENT);
michael@0 1625 }
michael@0 1626
michael@0 1627 void
michael@0 1628 nsHttpChannel::HandleAsyncAPIRedirect()
michael@0 1629 {
michael@0 1630 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
michael@0 1631 NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
michael@0 1632
michael@0 1633 if (mSuspendCount) {
michael@0 1634 LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
michael@0 1635 mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
michael@0 1636 return;
michael@0 1637 }
michael@0 1638
michael@0 1639 nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI,
michael@0 1640 nsIChannelEventSink::REDIRECT_PERMANENT);
michael@0 1641 if (NS_FAILED(rv))
michael@0 1642 ContinueAsyncRedirectChannelToURI(rv);
michael@0 1643
michael@0 1644 return;
michael@0 1645 }
michael@0 1646
michael@0 1647 nsresult
michael@0 1648 nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags)
michael@0 1649 {
michael@0 1650 nsresult rv = NS_OK;
michael@0 1651 LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
michael@0 1652
michael@0 1653 nsCOMPtr<nsIChannel> newChannel;
michael@0 1654
michael@0 1655 nsCOMPtr<nsIIOService> ioService;
michael@0 1656 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
michael@0 1657 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1658
michael@0 1659 rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel));
michael@0 1660 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1661
michael@0 1662 rv = SetupReplacementChannel(upgradedURI, newChannel, true);
michael@0 1663 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1664
michael@0 1665 // Inform consumers about this fake redirect
michael@0 1666 mRedirectChannel = newChannel;
michael@0 1667
michael@0 1668 PushRedirectAsyncFunc(
michael@0 1669 &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
michael@0 1670 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
michael@0 1671
michael@0 1672 if (NS_SUCCEEDED(rv))
michael@0 1673 rv = WaitForRedirectCallback();
michael@0 1674
michael@0 1675 if (NS_FAILED(rv)) {
michael@0 1676 AutoRedirectVetoNotifier notifier(this);
michael@0 1677
michael@0 1678 /* Remove the async call to ContinueAsyncRedirectChannelToURI().
michael@0 1679 * It is called directly by our callers upon return (to clean up
michael@0 1680 * the failed redirect). */
michael@0 1681 PopRedirectAsyncFunc(
michael@0 1682 &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
michael@0 1683 }
michael@0 1684
michael@0 1685 return rv;
michael@0 1686 }
michael@0 1687
michael@0 1688 nsresult
michael@0 1689 nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv)
michael@0 1690 {
michael@0 1691 if (NS_SUCCEEDED(rv))
michael@0 1692 rv = OpenRedirectChannel(rv);
michael@0 1693
michael@0 1694 if (NS_FAILED(rv)) {
michael@0 1695 // Fill the failure status here, the update to https had been vetoed
michael@0 1696 // but from the security reasons we have to discard the whole channel
michael@0 1697 // load.
michael@0 1698 mStatus = rv;
michael@0 1699 }
michael@0 1700
michael@0 1701 if (mLoadGroup)
michael@0 1702 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
michael@0 1703
michael@0 1704 if (NS_FAILED(rv)) {
michael@0 1705 // We have to manually notify the listener because there is not any pump
michael@0 1706 // that would call our OnStart/StopRequest after resume from waiting for
michael@0 1707 // the redirect callback.
michael@0 1708 DoNotifyListener();
michael@0 1709 }
michael@0 1710
michael@0 1711 return rv;
michael@0 1712 }
michael@0 1713
michael@0 1714 nsresult
michael@0 1715 nsHttpChannel::OpenRedirectChannel(nsresult rv)
michael@0 1716 {
michael@0 1717 AutoRedirectVetoNotifier notifier(this);
michael@0 1718
michael@0 1719 // Make sure to do this _after_ calling OnChannelRedirect
michael@0 1720 mRedirectChannel->SetOriginalURI(mOriginalURI);
michael@0 1721
michael@0 1722 // And now, notify observers the deprecated way
michael@0 1723 nsCOMPtr<nsIHttpEventSink> httpEventSink;
michael@0 1724 GetCallback(httpEventSink);
michael@0 1725 if (httpEventSink) {
michael@0 1726 // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
michael@0 1727 // versions.
michael@0 1728 rv = httpEventSink->OnRedirect(this, mRedirectChannel);
michael@0 1729 if (NS_FAILED(rv)) {
michael@0 1730 return rv;
michael@0 1731 }
michael@0 1732 }
michael@0 1733
michael@0 1734 // open new channel
michael@0 1735 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
michael@0 1736 if (NS_FAILED(rv)) {
michael@0 1737 return rv;
michael@0 1738 }
michael@0 1739
michael@0 1740 mStatus = NS_BINDING_REDIRECTED;
michael@0 1741
michael@0 1742 notifier.RedirectSucceeded();
michael@0 1743
michael@0 1744 ReleaseListeners();
michael@0 1745
michael@0 1746 return NS_OK;
michael@0 1747 }
michael@0 1748
michael@0 1749 nsresult
michael@0 1750 nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi)
michael@0 1751 {
michael@0 1752 LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
michael@0 1753 nsresult rv;
michael@0 1754
michael@0 1755 nsCOMPtr<nsIChannel> newChannel;
michael@0 1756 rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags,
michael@0 1757 mProxyURI, getter_AddRefs(newChannel));
michael@0 1758 if (NS_FAILED(rv))
michael@0 1759 return rv;
michael@0 1760
michael@0 1761 rv = SetupReplacementChannel(mURI, newChannel, true);
michael@0 1762 if (NS_FAILED(rv))
michael@0 1763 return rv;
michael@0 1764
michael@0 1765 // Inform consumers about this fake redirect
michael@0 1766 mRedirectChannel = newChannel;
michael@0 1767 uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
michael@0 1768
michael@0 1769 PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
michael@0 1770 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
michael@0 1771
michael@0 1772 if (NS_SUCCEEDED(rv))
michael@0 1773 rv = WaitForRedirectCallback();
michael@0 1774
michael@0 1775 if (NS_FAILED(rv)) {
michael@0 1776 AutoRedirectVetoNotifier notifier(this);
michael@0 1777 PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
michael@0 1778 }
michael@0 1779
michael@0 1780 return rv;
michael@0 1781 }
michael@0 1782
michael@0 1783 nsresult
michael@0 1784 nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv)
michael@0 1785 {
michael@0 1786 AutoRedirectVetoNotifier notifier(this);
michael@0 1787
michael@0 1788 if (NS_FAILED(rv))
michael@0 1789 return rv;
michael@0 1790
michael@0 1791 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
michael@0 1792
michael@0 1793 // Make sure to do this _after_ calling OnChannelRedirect
michael@0 1794 mRedirectChannel->SetOriginalURI(mOriginalURI);
michael@0 1795
michael@0 1796 // open new channel
michael@0 1797 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
michael@0 1798 if (NS_FAILED(rv))
michael@0 1799 return rv;
michael@0 1800
michael@0 1801 mStatus = NS_BINDING_REDIRECTED;
michael@0 1802
michael@0 1803 notifier.RedirectSucceeded();
michael@0 1804
michael@0 1805 ReleaseListeners();
michael@0 1806
michael@0 1807 return rv;
michael@0 1808 }
michael@0 1809
michael@0 1810 nsresult
michael@0 1811 nsHttpChannel::ResolveProxy()
michael@0 1812 {
michael@0 1813 LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
michael@0 1814
michael@0 1815 nsresult rv;
michael@0 1816
michael@0 1817 nsCOMPtr<nsIProtocolProxyService> pps =
michael@0 1818 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
michael@0 1819 if (NS_FAILED(rv))
michael@0 1820 return rv;
michael@0 1821
michael@0 1822 // using the nsIProtocolProxyService2 allows a minor performance
michael@0 1823 // optimization, but if an add-on has only provided the original interface
michael@0 1824 // then it is ok to use that version.
michael@0 1825 nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
michael@0 1826 if (pps2) {
michael@0 1827 rv = pps2->AsyncResolve2(this, mProxyResolveFlags,
michael@0 1828 this, getter_AddRefs(mProxyRequest));
michael@0 1829 } else {
michael@0 1830 rv = pps->AsyncResolve(this, mProxyResolveFlags,
michael@0 1831 this, getter_AddRefs(mProxyRequest));
michael@0 1832 }
michael@0 1833
michael@0 1834 return rv;
michael@0 1835 }
michael@0 1836
michael@0 1837 bool
michael@0 1838 nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const
michael@0 1839 {
michael@0 1840 nsresult rv;
michael@0 1841 nsAutoCString buf, metaKey;
michael@0 1842 mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
michael@0 1843 if (!buf.IsEmpty()) {
michael@0 1844 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
michael@0 1845
michael@0 1846 // enumerate the elements of the Vary header...
michael@0 1847 char *val = buf.BeginWriting(); // going to munge buf
michael@0 1848 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
michael@0 1849 while (token) {
michael@0 1850 LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " \
michael@0 1851 "processing %s\n",
michael@0 1852 this, token));
michael@0 1853 //
michael@0 1854 // if "*", then assume response would vary. technically speaking,
michael@0 1855 // "Vary: header, *" is not permitted, but we allow it anyways.
michael@0 1856 //
michael@0 1857 // We hash values of cookie-headers for the following reasons:
michael@0 1858 //
michael@0 1859 // 1- cookies can be very large in size
michael@0 1860 //
michael@0 1861 // 2- cookies may contain sensitive information. (for parity with
michael@0 1862 // out policy of not storing Set-cookie headers in the cache
michael@0 1863 // meta data, we likewise do not want to store cookie headers
michael@0 1864 // here.)
michael@0 1865 //
michael@0 1866 if (*token == '*')
michael@0 1867 return true; // if we encounter this, just get out of here
michael@0 1868
michael@0 1869 // build cache meta data key...
michael@0 1870 metaKey = prefix + nsDependentCString(token);
michael@0 1871
michael@0 1872 // check the last value of the given request header to see if it has
michael@0 1873 // since changed. if so, then indeed the cached response is invalid.
michael@0 1874 nsXPIDLCString lastVal;
michael@0 1875 entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
michael@0 1876 LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] "
michael@0 1877 "stored value = \"%s\"\n",
michael@0 1878 this, lastVal.get()));
michael@0 1879
michael@0 1880 // Look for value of "Cookie" in the request headers
michael@0 1881 nsHttpAtom atom = nsHttp::ResolveAtom(token);
michael@0 1882 const char *newVal = mRequestHead.PeekHeader(atom);
michael@0 1883 if (!lastVal.IsEmpty()) {
michael@0 1884 // value for this header in cache, but no value in request
michael@0 1885 if (!newVal)
michael@0 1886 return true; // yes - response would vary
michael@0 1887
michael@0 1888 // If this is a cookie-header, stored metadata is not
michael@0 1889 // the value itself but the hash. So we also hash the
michael@0 1890 // outgoing value here in order to compare the hashes
michael@0 1891 nsAutoCString hash;
michael@0 1892 if (atom == nsHttp::Cookie) {
michael@0 1893 rv = Hash(newVal, hash);
michael@0 1894 // If hash failed, be conservative (the cached hash
michael@0 1895 // exists at this point) and claim response would vary
michael@0 1896 if (NS_FAILED(rv))
michael@0 1897 return true;
michael@0 1898 newVal = hash.get();
michael@0 1899
michael@0 1900 LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \
michael@0 1901 "set-cookie value hashed to %s\n",
michael@0 1902 this, newVal));
michael@0 1903 }
michael@0 1904
michael@0 1905 if (strcmp(newVal, lastVal))
michael@0 1906 return true; // yes, response would vary
michael@0 1907
michael@0 1908 } else if (newVal) { // old value is empty, but newVal is set
michael@0 1909 return true;
michael@0 1910 }
michael@0 1911
michael@0 1912 // next token...
michael@0 1913 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
michael@0 1914 }
michael@0 1915 }
michael@0 1916 return false;
michael@0 1917 }
michael@0 1918
michael@0 1919 // We need to have an implementation of this function just so that we can keep
michael@0 1920 // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
michael@0 1921 // to set a member function ptr to a base class function.
michael@0 1922 void
michael@0 1923 nsHttpChannel::HandleAsyncAbort()
michael@0 1924 {
michael@0 1925 HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
michael@0 1926 }
michael@0 1927
michael@0 1928
michael@0 1929 nsresult
michael@0 1930 nsHttpChannel::EnsureAssocReq()
michael@0 1931 {
michael@0 1932 // Confirm Assoc-Req response header on pipelined transactions
michael@0 1933 // per draft-nottingham-http-pipeline-01.txt
michael@0 1934 // of the form: GET http://blah.com/foo/bar?qv
michael@0 1935 // return NS_OK as long as we don't find a violation
michael@0 1936 // (i.e. no header is ok, as are malformed headers, as are
michael@0 1937 // transactions that have not been pipelined (unless those have been
michael@0 1938 // opted in via pragma))
michael@0 1939
michael@0 1940 if (!mResponseHead)
michael@0 1941 return NS_OK;
michael@0 1942
michael@0 1943 const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req);
michael@0 1944 if (!assoc_val)
michael@0 1945 return NS_OK;
michael@0 1946
michael@0 1947 if (!mTransaction || !mURI)
michael@0 1948 return NS_OK;
michael@0 1949
michael@0 1950 if (!mTransaction->PipelinePosition()) {
michael@0 1951 // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
michael@0 1952 // transactions. It is used by test harness.
michael@0 1953
michael@0 1954 const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma);
michael@0 1955 if (!pragma_val ||
michael@0 1956 !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req",
michael@0 1957 HTTP_HEADER_VALUE_SEPS))
michael@0 1958 return NS_OK;
michael@0 1959 }
michael@0 1960
michael@0 1961 char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS);
michael@0 1962 if (!method)
michael@0 1963 return NS_OK;
michael@0 1964
michael@0 1965 bool equals;
michael@0 1966 char *endofmethod;
michael@0 1967
michael@0 1968 assoc_val = nullptr;
michael@0 1969 endofmethod = net_FindCharInSet(method, HTTP_LWS);
michael@0 1970 if (endofmethod)
michael@0 1971 assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS);
michael@0 1972 if (!assoc_val)
michael@0 1973 return NS_OK;
michael@0 1974
michael@0 1975 // check the method
michael@0 1976 int32_t methodlen = strlen(mRequestHead.Method().get());
michael@0 1977 if ((methodlen != (endofmethod - method)) ||
michael@0 1978 PL_strncmp(method,
michael@0 1979 mRequestHead.Method().get(),
michael@0 1980 endofmethod - method)) {
michael@0 1981 LOG((" Assoc-Req failure Method %s", method));
michael@0 1982 if (mConnectionInfo)
michael@0 1983 gHttpHandler->ConnMgr()->
michael@0 1984 PipelineFeedbackInfo(mConnectionInfo,
michael@0 1985 nsHttpConnectionMgr::RedCorruptedContent,
michael@0 1986 nullptr, 0);
michael@0 1987
michael@0 1988 nsCOMPtr<nsIConsoleService> consoleService =
michael@0 1989 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
michael@0 1990 if (consoleService) {
michael@0 1991 nsAutoString message
michael@0 1992 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
michael@0 1993 AppendASCIItoUTF16(
michael@0 1994 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
michael@0 1995 message);
michael@0 1996 message += NS_LITERAL_STRING(" expected method ");
michael@0 1997 AppendASCIItoUTF16(mRequestHead.Method().get(), message);
michael@0 1998 consoleService->LogStringMessage(message.get());
michael@0 1999 }
michael@0 2000
michael@0 2001 if (gHttpHandler->EnforceAssocReq())
michael@0 2002 return NS_ERROR_CORRUPTED_CONTENT;
michael@0 2003 return NS_OK;
michael@0 2004 }
michael@0 2005
michael@0 2006 // check the URL
michael@0 2007 nsCOMPtr<nsIURI> assoc_url;
michael@0 2008 if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) ||
michael@0 2009 !assoc_url)
michael@0 2010 return NS_OK;
michael@0 2011
michael@0 2012 mURI->Equals(assoc_url, &equals);
michael@0 2013 if (!equals) {
michael@0 2014 LOG((" Assoc-Req failure URL %s", assoc_val));
michael@0 2015 if (mConnectionInfo)
michael@0 2016 gHttpHandler->ConnMgr()->
michael@0 2017 PipelineFeedbackInfo(mConnectionInfo,
michael@0 2018 nsHttpConnectionMgr::RedCorruptedContent,
michael@0 2019 nullptr, 0);
michael@0 2020
michael@0 2021 nsCOMPtr<nsIConsoleService> consoleService =
michael@0 2022 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
michael@0 2023 if (consoleService) {
michael@0 2024 nsAutoString message
michael@0 2025 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
michael@0 2026 AppendASCIItoUTF16(
michael@0 2027 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
michael@0 2028 message);
michael@0 2029 message += NS_LITERAL_STRING(" expected URL ");
michael@0 2030 AppendASCIItoUTF16(mSpec.get(), message);
michael@0 2031 consoleService->LogStringMessage(message.get());
michael@0 2032 }
michael@0 2033
michael@0 2034 if (gHttpHandler->EnforceAssocReq())
michael@0 2035 return NS_ERROR_CORRUPTED_CONTENT;
michael@0 2036 }
michael@0 2037 return NS_OK;
michael@0 2038 }
michael@0 2039
michael@0 2040 //-----------------------------------------------------------------------------
michael@0 2041 // nsHttpChannel <byte-range>
michael@0 2042 //-----------------------------------------------------------------------------
michael@0 2043
michael@0 2044 bool
michael@0 2045 nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
michael@0 2046 bool ignoreMissingPartialLen) const
michael@0 2047 {
michael@0 2048 bool hasContentEncoding =
michael@0 2049 mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)
michael@0 2050 != nullptr;
michael@0 2051
michael@0 2052 return (partialLen < contentLength) &&
michael@0 2053 (partialLen > 0 || ignoreMissingPartialLen) &&
michael@0 2054 !hasContentEncoding &&
michael@0 2055 mCachedResponseHead->IsResumable() &&
michael@0 2056 !mCustomConditionalRequest &&
michael@0 2057 !mCachedResponseHead->NoStore();
michael@0 2058 }
michael@0 2059
michael@0 2060 nsresult
michael@0 2061 nsHttpChannel::MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength)
michael@0 2062 {
michael@0 2063 // Be pesimistic
michael@0 2064 mIsPartialRequest = false;
michael@0 2065
michael@0 2066 if (!IsResumable(partialLen, contentLength))
michael@0 2067 return NS_ERROR_NOT_RESUMABLE;
michael@0 2068
michael@0 2069 // looks like a partial entry we can reuse; add If-Range
michael@0 2070 // and Range headers.
michael@0 2071 nsresult rv = SetupByteRangeRequest(partialLen);
michael@0 2072 if (NS_FAILED(rv)) {
michael@0 2073 // Make the request unconditional again.
michael@0 2074 mRequestHead.ClearHeader(nsHttp::Range);
michael@0 2075 mRequestHead.ClearHeader(nsHttp::If_Range);
michael@0 2076 }
michael@0 2077
michael@0 2078 return rv;
michael@0 2079 }
michael@0 2080
michael@0 2081 nsresult
michael@0 2082 nsHttpChannel::SetupByteRangeRequest(int64_t partialLen)
michael@0 2083 {
michael@0 2084 // cached content has been found to be partial, add necessary request
michael@0 2085 // headers to complete cache entry.
michael@0 2086
michael@0 2087 // use strongest validator available...
michael@0 2088 const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
michael@0 2089 if (!val)
michael@0 2090 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
michael@0 2091 if (!val) {
michael@0 2092 // if we hit this code it means mCachedResponseHead->IsResumable() is
michael@0 2093 // either broken or not being called.
michael@0 2094 NS_NOTREACHED("no cache validator");
michael@0 2095 mIsPartialRequest = false;
michael@0 2096 return NS_ERROR_FAILURE;
michael@0 2097 }
michael@0 2098
michael@0 2099 char buf[64];
michael@0 2100 PR_snprintf(buf, sizeof(buf), "bytes=%lld-", partialLen);
michael@0 2101
michael@0 2102 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
michael@0 2103 mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
michael@0 2104 mIsPartialRequest = true;
michael@0 2105
michael@0 2106 return NS_OK;
michael@0 2107 }
michael@0 2108
michael@0 2109 nsresult
michael@0 2110 nsHttpChannel::ProcessPartialContent()
michael@0 2111 {
michael@0 2112 // ok, we've just received a 206
michael@0 2113 //
michael@0 2114 // we need to stream whatever data is in the cache out first, and then
michael@0 2115 // pick up whatever data is on the wire, writing it into the cache.
michael@0 2116
michael@0 2117 LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
michael@0 2118
michael@0 2119 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
michael@0 2120 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
michael@0 2121
michael@0 2122 // Make sure to clear bogus content-encodings before looking at the header
michael@0 2123 ClearBogusContentEncodingIfNeeded();
michael@0 2124
michael@0 2125 // Check if the content-encoding we now got is different from the one we
michael@0 2126 // got before
michael@0 2127 if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
michael@0 2128 mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
michael@0 2129 != 0) {
michael@0 2130 Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
michael@0 2131 return CallOnStartRequest();
michael@0 2132 }
michael@0 2133
michael@0 2134 nsresult rv;
michael@0 2135
michael@0 2136 int64_t cachedContentLength = mCachedResponseHead->ContentLength();
michael@0 2137 int64_t entitySize = mResponseHead->TotalEntitySize();
michael@0 2138
michael@0 2139 LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
michael@0 2140 "original content-length %lld, entity-size %lld, content-range %s\n",
michael@0 2141 this, mTransaction.get(), cachedContentLength, entitySize,
michael@0 2142 mResponseHead->PeekHeader(nsHttp::Content_Range)));
michael@0 2143
michael@0 2144 if ((entitySize >= 0) && (cachedContentLength >= 0) &&
michael@0 2145 (entitySize != cachedContentLength)) {
michael@0 2146 LOG(("nsHttpChannel::ProcessPartialContent [this=%p] "
michael@0 2147 "206 has different total entity size than the content length "
michael@0 2148 "of the original partially cached entity.\n", this));
michael@0 2149
michael@0 2150 mCacheEntry->AsyncDoom(nullptr);
michael@0 2151 Cancel(NS_ERROR_CORRUPTED_CONTENT);
michael@0 2152 return CallOnStartRequest();
michael@0 2153 }
michael@0 2154
michael@0 2155 if (mConcurentCacheAccess) {
michael@0 2156 // We started to read cached data sooner than its write has been done.
michael@0 2157 // But the concurrent write has not finished completely, so we had to
michael@0 2158 // do a range request. Now let the content coming from the network
michael@0 2159 // be presented to consumers and also stored to the cache entry.
michael@0 2160
michael@0 2161 rv = InstallCacheListener(mLogicalOffset);
michael@0 2162 if (NS_FAILED(rv)) return rv;
michael@0 2163
michael@0 2164 if (mOfflineCacheEntry) {
michael@0 2165 rv = InstallOfflineCacheListener(mLogicalOffset);
michael@0 2166 if (NS_FAILED(rv)) return rv;
michael@0 2167 }
michael@0 2168
michael@0 2169 // merge any new headers with the cached response headers
michael@0 2170 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
michael@0 2171 if (NS_FAILED(rv)) return rv;
michael@0 2172
michael@0 2173 // update the cached response head
michael@0 2174 nsAutoCString head;
michael@0 2175 mCachedResponseHead->Flatten(head, true);
michael@0 2176 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
michael@0 2177 if (NS_FAILED(rv)) return rv;
michael@0 2178
michael@0 2179 UpdateInhibitPersistentCachingFlag();
michael@0 2180
michael@0 2181 rv = UpdateExpirationTime();
michael@0 2182 if (NS_FAILED(rv)) return rv;
michael@0 2183
michael@0 2184 mCachedContentIsPartial = false;
michael@0 2185
michael@0 2186 // notify observers interested in looking at a response that has been
michael@0 2187 // merged with any cached headers (http-on-examine-merged-response).
michael@0 2188 gHttpHandler->OnExamineMergedResponse(this);
michael@0 2189 }
michael@0 2190 else {
michael@0 2191 // suspend the current transaction
michael@0 2192 rv = mTransactionPump->Suspend();
michael@0 2193 if (NS_FAILED(rv)) return rv;
michael@0 2194
michael@0 2195 // merge any new headers with the cached response headers
michael@0 2196 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
michael@0 2197 if (NS_FAILED(rv)) return rv;
michael@0 2198
michael@0 2199 // update the cached response head
michael@0 2200 nsAutoCString head;
michael@0 2201 mCachedResponseHead->Flatten(head, true);
michael@0 2202 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
michael@0 2203 if (NS_FAILED(rv)) return rv;
michael@0 2204
michael@0 2205 // make the cached response be the current response
michael@0 2206 mResponseHead = mCachedResponseHead;
michael@0 2207
michael@0 2208 UpdateInhibitPersistentCachingFlag();
michael@0 2209
michael@0 2210 rv = UpdateExpirationTime();
michael@0 2211 if (NS_FAILED(rv)) return rv;
michael@0 2212
michael@0 2213 // notify observers interested in looking at a response that has been
michael@0 2214 // merged with any cached headers (http-on-examine-merged-response).
michael@0 2215 gHttpHandler->OnExamineMergedResponse(this);
michael@0 2216
michael@0 2217 // the cached content is valid, although incomplete.
michael@0 2218 mCachedContentIsValid = true;
michael@0 2219 rv = ReadFromCache(false);
michael@0 2220 }
michael@0 2221
michael@0 2222 return rv;
michael@0 2223 }
michael@0 2224
michael@0 2225 nsresult
michael@0 2226 nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone)
michael@0 2227 {
michael@0 2228 nsresult rv;
michael@0 2229
michael@0 2230 LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
michael@0 2231
michael@0 2232 // by default, assume we would have streamed all data or failed...
michael@0 2233 *streamDone = true;
michael@0 2234
michael@0 2235 // setup cache listener to append to cache entry
michael@0 2236 int64_t size;
michael@0 2237 rv = mCacheEntry->GetDataSize(&size);
michael@0 2238 if (NS_FAILED(rv)) return rv;
michael@0 2239
michael@0 2240 rv = InstallCacheListener(size);
michael@0 2241 if (NS_FAILED(rv)) return rv;
michael@0 2242
michael@0 2243 // Entry is valid, do it now, after the output stream has been opened,
michael@0 2244 // otherwise when done earlier, pending readers would consider the cache
michael@0 2245 // entry still as partial (CacheEntry::GetDataSize would return the partial
michael@0 2246 // data size) and consumers would do the conditional request again.
michael@0 2247 rv = mCacheEntry->SetValid();
michael@0 2248 if (NS_FAILED(rv)) return rv;
michael@0 2249
michael@0 2250 // need to track the logical offset of the data being sent to our listener
michael@0 2251 mLogicalOffset = size;
michael@0 2252
michael@0 2253 // we're now completing the cached content, so we can clear this flag.
michael@0 2254 // this puts us in the state of a regular download.
michael@0 2255 mCachedContentIsPartial = false;
michael@0 2256
michael@0 2257 // resume the transaction if it exists, otherwise the pipe contained the
michael@0 2258 // remaining part of the document and we've now streamed all of the data.
michael@0 2259 if (mTransactionPump) {
michael@0 2260 rv = mTransactionPump->Resume();
michael@0 2261 if (NS_SUCCEEDED(rv))
michael@0 2262 *streamDone = false;
michael@0 2263 }
michael@0 2264 else
michael@0 2265 NS_NOTREACHED("no transaction");
michael@0 2266 return rv;
michael@0 2267 }
michael@0 2268
michael@0 2269 //-----------------------------------------------------------------------------
michael@0 2270 // nsHttpChannel <cache>
michael@0 2271 //-----------------------------------------------------------------------------
michael@0 2272
michael@0 2273 nsresult
michael@0 2274 nsHttpChannel::ProcessNotModified()
michael@0 2275 {
michael@0 2276 nsresult rv;
michael@0 2277
michael@0 2278 LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
michael@0 2279
michael@0 2280 if (mCustomConditionalRequest) {
michael@0 2281 LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
michael@0 2282 return NS_ERROR_FAILURE;
michael@0 2283 }
michael@0 2284
michael@0 2285 if (!mDidReval) {
michael@0 2286 LOG(("Server returned a 304 response even though we did not send a "
michael@0 2287 "conditional request"));
michael@0 2288 return NS_ERROR_FAILURE;
michael@0 2289 }
michael@0 2290
michael@0 2291 MOZ_ASSERT(mCachedResponseHead);
michael@0 2292 MOZ_ASSERT(mCacheEntry);
michael@0 2293 NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
michael@0 2294
michael@0 2295 // If the 304 response contains a Last-Modified different than the
michael@0 2296 // one in our cache that is pretty suspicious and is, in at least the
michael@0 2297 // case of bug 716840, a sign of the server having previously corrupted
michael@0 2298 // our cache with a bad response. Take the minor step here of just dooming
michael@0 2299 // that cache entry so there is a fighting chance of getting things on the
michael@0 2300 // right track as well as disabling pipelining for that host.
michael@0 2301
michael@0 2302 nsAutoCString lastModifiedCached;
michael@0 2303 nsAutoCString lastModified304;
michael@0 2304
michael@0 2305 rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified,
michael@0 2306 lastModifiedCached);
michael@0 2307 if (NS_SUCCEEDED(rv)) {
michael@0 2308 rv = mResponseHead->GetHeader(nsHttp::Last_Modified,
michael@0 2309 lastModified304);
michael@0 2310 }
michael@0 2311
michael@0 2312 if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
michael@0 2313 LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match "
michael@0 2314 "[%s] and [%s]\n",
michael@0 2315 lastModifiedCached.get(), lastModified304.get()));
michael@0 2316
michael@0 2317 mCacheEntry->AsyncDoom(nullptr);
michael@0 2318 if (mConnectionInfo)
michael@0 2319 gHttpHandler->ConnMgr()->
michael@0 2320 PipelineFeedbackInfo(mConnectionInfo,
michael@0 2321 nsHttpConnectionMgr::RedCorruptedContent,
michael@0 2322 nullptr, 0);
michael@0 2323 Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
michael@0 2324 }
michael@0 2325
michael@0 2326 // merge any new headers with the cached response headers
michael@0 2327 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
michael@0 2328 if (NS_FAILED(rv)) return rv;
michael@0 2329
michael@0 2330 // update the cached response head
michael@0 2331 nsAutoCString head;
michael@0 2332 mCachedResponseHead->Flatten(head, true);
michael@0 2333 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
michael@0 2334 if (NS_FAILED(rv)) return rv;
michael@0 2335
michael@0 2336 // make the cached response be the current response
michael@0 2337 mResponseHead = mCachedResponseHead;
michael@0 2338
michael@0 2339 UpdateInhibitPersistentCachingFlag();
michael@0 2340
michael@0 2341 rv = UpdateExpirationTime();
michael@0 2342 if (NS_FAILED(rv)) return rv;
michael@0 2343
michael@0 2344 rv = AddCacheEntryHeaders(mCacheEntry);
michael@0 2345 if (NS_FAILED(rv)) return rv;
michael@0 2346
michael@0 2347 // notify observers interested in looking at a reponse that has been
michael@0 2348 // merged with any cached headers
michael@0 2349 gHttpHandler->OnExamineMergedResponse(this);
michael@0 2350
michael@0 2351 mCachedContentIsValid = true;
michael@0 2352
michael@0 2353 // Tell other consumers the entry is OK to use
michael@0 2354 rv = mCacheEntry->SetValid();
michael@0 2355 if (NS_FAILED(rv)) return rv;
michael@0 2356
michael@0 2357 rv = ReadFromCache(false);
michael@0 2358 if (NS_FAILED(rv)) return rv;
michael@0 2359
michael@0 2360 mTransactionReplaced = true;
michael@0 2361 return NS_OK;
michael@0 2362 }
michael@0 2363
michael@0 2364 nsresult
michael@0 2365 nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
michael@0 2366 {
michael@0 2367 LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
michael@0 2368 nsresult rv;
michael@0 2369
michael@0 2370 *waitingForRedirectCallback = false;
michael@0 2371 mFallingBack = false;
michael@0 2372
michael@0 2373 // At this point a load has failed (either due to network problems
michael@0 2374 // or an error returned on the server). Perform an application
michael@0 2375 // cache fallback if we have a URI to fall back to.
michael@0 2376 if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
michael@0 2377 LOG((" choosing not to fallback [%p,%s,%d]",
michael@0 2378 mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
michael@0 2379 return NS_OK;
michael@0 2380 }
michael@0 2381
michael@0 2382 // Make sure the fallback entry hasn't been marked as a foreign
michael@0 2383 // entry.
michael@0 2384 uint32_t fallbackEntryType;
michael@0 2385 rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
michael@0 2386 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2387
michael@0 2388 if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
michael@0 2389 // This cache points to a fallback that refers to a different
michael@0 2390 // manifest. Refuse to fall back.
michael@0 2391 return NS_OK;
michael@0 2392 }
michael@0 2393
michael@0 2394 MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
michael@0 2395 "Fallback entry not marked correctly!");
michael@0 2396
michael@0 2397 // Kill any offline cache entry, and disable offline caching for the
michael@0 2398 // fallback.
michael@0 2399 if (mOfflineCacheEntry) {
michael@0 2400 mOfflineCacheEntry->AsyncDoom(nullptr);
michael@0 2401 mOfflineCacheEntry = nullptr;
michael@0 2402 }
michael@0 2403
michael@0 2404 mApplicationCacheForWrite = nullptr;
michael@0 2405 mOfflineCacheEntry = nullptr;
michael@0 2406
michael@0 2407 // Close the current cache entry.
michael@0 2408 CloseCacheEntry(true);
michael@0 2409
michael@0 2410 // Create a new channel to load the fallback entry.
michael@0 2411 nsRefPtr<nsIChannel> newChannel;
michael@0 2412 rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
michael@0 2413 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2414
michael@0 2415 rv = SetupReplacementChannel(mURI, newChannel, true);
michael@0 2416 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2417
michael@0 2418 // Make sure the new channel loads from the fallback key.
michael@0 2419 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
michael@0 2420 do_QueryInterface(newChannel, &rv);
michael@0 2421 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2422
michael@0 2423 rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
michael@0 2424 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2425
michael@0 2426 // ... and fallbacks should only load from the cache.
michael@0 2427 uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
michael@0 2428 rv = newChannel->SetLoadFlags(newLoadFlags);
michael@0 2429
michael@0 2430 // Inform consumers about this fake redirect
michael@0 2431 mRedirectChannel = newChannel;
michael@0 2432 uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
michael@0 2433
michael@0 2434 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
michael@0 2435 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
michael@0 2436
michael@0 2437 if (NS_SUCCEEDED(rv))
michael@0 2438 rv = WaitForRedirectCallback();
michael@0 2439
michael@0 2440 if (NS_FAILED(rv)) {
michael@0 2441 AutoRedirectVetoNotifier notifier(this);
michael@0 2442 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
michael@0 2443 return rv;
michael@0 2444 }
michael@0 2445
michael@0 2446 // Indicate we are now waiting for the asynchronous redirect callback
michael@0 2447 // if all went OK.
michael@0 2448 *waitingForRedirectCallback = true;
michael@0 2449 return NS_OK;
michael@0 2450 }
michael@0 2451
michael@0 2452 nsresult
michael@0 2453 nsHttpChannel::ContinueProcessFallback(nsresult rv)
michael@0 2454 {
michael@0 2455 AutoRedirectVetoNotifier notifier(this);
michael@0 2456
michael@0 2457 if (NS_FAILED(rv))
michael@0 2458 return rv;
michael@0 2459
michael@0 2460 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
michael@0 2461
michael@0 2462 // Make sure to do this _after_ calling OnChannelRedirect
michael@0 2463 mRedirectChannel->SetOriginalURI(mOriginalURI);
michael@0 2464
michael@0 2465 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
michael@0 2466 if (NS_FAILED(rv))
michael@0 2467 return rv;
michael@0 2468
michael@0 2469 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
michael@0 2470 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
michael@0 2471 true);
michael@0 2472 }
michael@0 2473
michael@0 2474 // close down this channel
michael@0 2475 Cancel(NS_BINDING_REDIRECTED);
michael@0 2476
michael@0 2477 notifier.RedirectSucceeded();
michael@0 2478
michael@0 2479 ReleaseListeners();
michael@0 2480
michael@0 2481 mFallingBack = true;
michael@0 2482
michael@0 2483 return NS_OK;
michael@0 2484 }
michael@0 2485
michael@0 2486 // Determines if a request is a byte range request for a subrange,
michael@0 2487 // i.e. is a byte range request, but not a 0- byte range request.
michael@0 2488 static bool
michael@0 2489 IsSubRangeRequest(nsHttpRequestHead &aRequestHead)
michael@0 2490 {
michael@0 2491 if (!aRequestHead.PeekHeader(nsHttp::Range))
michael@0 2492 return false;
michael@0 2493 nsAutoCString byteRange;
michael@0 2494 aRequestHead.GetHeader(nsHttp::Range, byteRange);
michael@0 2495 return !byteRange.EqualsLiteral("bytes=0-");
michael@0 2496 }
michael@0 2497
michael@0 2498 nsresult
michael@0 2499 nsHttpChannel::OpenCacheEntry(bool usingSSL)
michael@0 2500 {
michael@0 2501 MOZ_EVENT_TRACER_EXEC(this, "net::http::OpenCacheEntry");
michael@0 2502
michael@0 2503 // Handle correctly mCacheEntriesToWaitFor
michael@0 2504 AutoCacheWaitFlags waitFlags(this);
michael@0 2505
michael@0 2506 // Drop this flag here
michael@0 2507 mConcurentCacheAccess = 0;
michael@0 2508
michael@0 2509 nsresult rv;
michael@0 2510
michael@0 2511 mLoadedFromApplicationCache = false;
michael@0 2512 mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
michael@0 2513
michael@0 2514 LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
michael@0 2515
michael@0 2516 // make sure we're not abusing this function
michael@0 2517 NS_PRECONDITION(!mCacheEntry, "cache entry already open");
michael@0 2518
michael@0 2519 nsAutoCString cacheKey;
michael@0 2520
michael@0 2521 if (mRequestHead.IsPost()) {
michael@0 2522 // If the post id is already set then this is an attempt to replay
michael@0 2523 // a post transaction via the cache. Otherwise, we need a unique
michael@0 2524 // post id for this transaction.
michael@0 2525 if (mPostID == 0)
michael@0 2526 mPostID = gHttpHandler->GenerateUniqueID();
michael@0 2527 }
michael@0 2528 else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
michael@0 2529 // don't use the cache for other types of requests
michael@0 2530 return NS_OK;
michael@0 2531 }
michael@0 2532
michael@0 2533 if (mResuming) {
michael@0 2534 // We don't support caching for requests initiated
michael@0 2535 // via nsIResumableChannel.
michael@0 2536 return NS_OK;
michael@0 2537 }
michael@0 2538
michael@0 2539 // Don't cache byte range requests which are subranges, only cache 0-
michael@0 2540 // byte range requests.
michael@0 2541 if (IsSubRangeRequest(mRequestHead))
michael@0 2542 return NS_OK;
michael@0 2543
michael@0 2544 // Pick up an application cache from the notification
michael@0 2545 // callbacks if available
michael@0 2546 if (!mApplicationCache && mInheritApplicationCache) {
michael@0 2547 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
michael@0 2548 GetCallback(appCacheContainer);
michael@0 2549
michael@0 2550 if (appCacheContainer) {
michael@0 2551 appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
michael@0 2552 }
michael@0 2553 }
michael@0 2554
michael@0 2555 nsCOMPtr<nsICacheStorageService> cacheStorageService =
michael@0 2556 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
michael@0 2557 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2558
michael@0 2559 nsRefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
michael@0 2560 nsCOMPtr<nsICacheStorage> cacheStorage;
michael@0 2561 nsCOMPtr<nsIURI> openURI;
michael@0 2562
michael@0 2563 /* Obtain optional third party isolation domain */
michael@0 2564 nsAutoCString cacheDomain;
michael@0 2565 nsCOMPtr<nsIURI> firstPartyIsolationURI;
michael@0 2566 nsCOMPtr<mozIThirdPartyUtil> thirdPartySvc
michael@0 2567 = do_GetService(THIRDPARTYUTIL_CONTRACTID);
michael@0 2568 rv = thirdPartySvc->GetFirstPartyIsolationURI(this, nullptr,
michael@0 2569 getter_AddRefs(firstPartyIsolationURI));
michael@0 2570 if (NS_SUCCEEDED(rv) && firstPartyIsolationURI) {
michael@0 2571 thirdPartySvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI,
michael@0 2572 cacheDomain);
michael@0 2573 }
michael@0 2574
michael@0 2575 if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
michael@0 2576 // This is a fallback channel, open fallback URI instead
michael@0 2577 rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
michael@0 2578 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2579 }
michael@0 2580 else {
michael@0 2581 openURI = mURI;
michael@0 2582 }
michael@0 2583
michael@0 2584 uint32_t cacheEntryOpenFlags;
michael@0 2585 bool offline = gIOService->IsOffline();
michael@0 2586 if (offline || (mLoadFlags & INHIBIT_CACHING)) {
michael@0 2587 if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) {
michael@0 2588 goto bypassCacheEntryOpen;
michael@0 2589 }
michael@0 2590 cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
michael@0 2591 mCacheEntryIsReadOnly = true;
michael@0 2592 }
michael@0 2593 else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
michael@0 2594 cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
michael@0 2595 }
michael@0 2596 else {
michael@0 2597 cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
michael@0 2598 | nsICacheStorage::CHECK_MULTITHREADED;
michael@0 2599 }
michael@0 2600
michael@0 2601 if (mApplicationCache) {
michael@0 2602 rv = cacheStorageService->AppCacheStorage(info,
michael@0 2603 mApplicationCache,
michael@0 2604 getter_AddRefs(cacheStorage));
michael@0 2605 }
michael@0 2606 else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
michael@0 2607 rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
michael@0 2608 getter_AddRefs(cacheStorage));
michael@0 2609 }
michael@0 2610 else {
michael@0 2611 rv = cacheStorageService->DiskCacheStorage(info,
michael@0 2612 mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE),
michael@0 2613 getter_AddRefs(cacheStorage));
michael@0 2614 }
michael@0 2615 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2616
michael@0 2617 // Don't consider mLoadUnblocked here, since it's not indication of a demand
michael@0 2618 // to load prioritly. It's mostly used to load XHR requests, but those should
michael@0 2619 // not be considered as influencing the page load performance.
michael@0 2620 if (mLoadAsBlocking || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
michael@0 2621 cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
michael@0 2622
michael@0 2623 // Only for backward compatibility with the old cache back end.
michael@0 2624 // When removed, remove the flags and related code snippets.
michael@0 2625 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
michael@0 2626 cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
michael@0 2627
michael@0 2628 rv = cacheStorage->AsyncOpenURI(
michael@0 2629 openURI, nsPrintfCString("%s@%d", cacheDomain.get(), mPostID),
michael@0 2630 cacheEntryOpenFlags, this);
michael@0 2631 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2632
michael@0 2633 waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
michael@0 2634
michael@0 2635 bypassCacheEntryOpen:
michael@0 2636 if (!mApplicationCacheForWrite)
michael@0 2637 return NS_OK;
michael@0 2638
michael@0 2639 // If there is an app cache to write to, open the entry right now in parallel.
michael@0 2640
michael@0 2641 // make sure we're not abusing this function
michael@0 2642 NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
michael@0 2643
michael@0 2644 if (offline) {
michael@0 2645 // only put things in the offline cache while online
michael@0 2646 return NS_OK;
michael@0 2647 }
michael@0 2648
michael@0 2649 if (mLoadFlags & INHIBIT_CACHING) {
michael@0 2650 // respect demand not to cache
michael@0 2651 return NS_OK;
michael@0 2652 }
michael@0 2653
michael@0 2654 if (!mRequestHead.IsGet()) {
michael@0 2655 // only cache complete documents offline
michael@0 2656 return NS_OK;
michael@0 2657 }
michael@0 2658
michael@0 2659 rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
michael@0 2660 getter_AddRefs(cacheStorage));
michael@0 2661 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2662
michael@0 2663 rv = cacheStorage->AsyncOpenURI(
michael@0 2664 mURI, cacheDomain, nsICacheStorage::OPEN_TRUNCATE, this);
michael@0 2665 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2666
michael@0 2667 waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
michael@0 2668
michael@0 2669 return NS_OK;
michael@0 2670 }
michael@0 2671
michael@0 2672 nsresult
michael@0 2673 nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength)
michael@0 2674 {
michael@0 2675 nsresult rv;
michael@0 2676
michael@0 2677 rv = aEntry->GetDataSize(aSize);
michael@0 2678
michael@0 2679 if (NS_ERROR_IN_PROGRESS == rv) {
michael@0 2680 *aSize = -1;
michael@0 2681 rv = NS_OK;
michael@0 2682 }
michael@0 2683
michael@0 2684 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2685
michael@0 2686 nsHttpResponseHead* responseHead = mCachedResponseHead
michael@0 2687 ? mCachedResponseHead
michael@0 2688 : mResponseHead;
michael@0 2689
michael@0 2690 if (!responseHead)
michael@0 2691 return NS_ERROR_UNEXPECTED;
michael@0 2692
michael@0 2693 *aContentLength = responseHead->ContentLength();
michael@0 2694
michael@0 2695 return NS_OK;
michael@0 2696 }
michael@0 2697
michael@0 2698 NS_IMETHODIMP
michael@0 2699 nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
michael@0 2700 uint32_t* aResult)
michael@0 2701 {
michael@0 2702 nsresult rv = NS_OK;
michael@0 2703
michael@0 2704 LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]",
michael@0 2705 this, entry));
michael@0 2706
michael@0 2707 // Remember the request is a custom conditional request so that we can
michael@0 2708 // process any 304 response correctly.
michael@0 2709 mCustomConditionalRequest =
michael@0 2710 mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
michael@0 2711 mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
michael@0 2712 mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
michael@0 2713 mRequestHead.PeekHeader(nsHttp::If_Match) ||
michael@0 2714 mRequestHead.PeekHeader(nsHttp::If_Range);
michael@0 2715
michael@0 2716 // Be pessimistic: assume the cache entry has no useful data.
michael@0 2717 *aResult = ENTRY_WANTED;
michael@0 2718 mCachedContentIsValid = false;
michael@0 2719
michael@0 2720 nsXPIDLCString buf;
michael@0 2721
michael@0 2722 // Get the method that was used to generate the cached response
michael@0 2723 rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
michael@0 2724 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2725
michael@0 2726 bool methodWasHead = buf.EqualsLiteral("HEAD");
michael@0 2727 bool methodWasGet = buf.EqualsLiteral("GET");
michael@0 2728
michael@0 2729 if (methodWasHead) {
michael@0 2730 // The cached response does not contain an entity. We can only reuse
michael@0 2731 // the response if the current request is also HEAD.
michael@0 2732 if (!mRequestHead.IsHead()) {
michael@0 2733 return NS_OK;
michael@0 2734 }
michael@0 2735 }
michael@0 2736 buf.Adopt(0);
michael@0 2737
michael@0 2738 // We'll need this value in later computations...
michael@0 2739 uint32_t lastModifiedTime;
michael@0 2740 rv = entry->GetLastModified(&lastModifiedTime);
michael@0 2741 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2742
michael@0 2743 // Determine if this is the first time that this cache entry
michael@0 2744 // has been accessed during this session.
michael@0 2745 bool fromPreviousSession =
michael@0 2746 (gHttpHandler->SessionStartTime() > lastModifiedTime);
michael@0 2747
michael@0 2748 // Get the cached HTTP response headers
michael@0 2749 rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
michael@0 2750 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2751
michael@0 2752 // Parse the cached HTTP response headers
michael@0 2753 mCachedResponseHead = new nsHttpResponseHead();
michael@0 2754 rv = mCachedResponseHead->Parse((char *) buf.get());
michael@0 2755 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2756 buf.Adopt(0);
michael@0 2757
michael@0 2758 bool isCachedRedirect = WillRedirect(mCachedResponseHead);
michael@0 2759
michael@0 2760 // Do not return 304 responses from the cache, and also do not return
michael@0 2761 // any other non-redirect 3xx responses from the cache (see bug 759043).
michael@0 2762 NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) ||
michael@0 2763 isCachedRedirect, NS_ERROR_ABORT);
michael@0 2764
michael@0 2765 // Don't bother to validate items that are read-only,
michael@0 2766 // unless they are read-only because of INHIBIT_CACHING or because
michael@0 2767 // we're updating the offline cache.
michael@0 2768 // Don't bother to validate if this is a fallback entry.
michael@0 2769 if (!mApplicationCacheForWrite &&
michael@0 2770 (appCache ||
michael@0 2771 (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
michael@0 2772 mFallbackChannel)) {
michael@0 2773 rv = OpenCacheInputStream(entry, true);
michael@0 2774 if (NS_SUCCEEDED(rv)) {
michael@0 2775 mCachedContentIsValid = true;
michael@0 2776 entry->MaybeMarkValid();
michael@0 2777 }
michael@0 2778 return rv;
michael@0 2779 }
michael@0 2780
michael@0 2781 bool wantCompleteEntry = false;
michael@0 2782
michael@0 2783 if (!methodWasHead && !isCachedRedirect) {
michael@0 2784 // If the cached content-length is set and it does not match the data
michael@0 2785 // size of the cached content, then the cached response is partial...
michael@0 2786 // either we need to issue a byte range request or we need to refetch
michael@0 2787 // the entire document.
michael@0 2788 //
michael@0 2789 // We exclude redirects from this check because we (usually) strip the
michael@0 2790 // entity when we store the cache entry, and even if we didn't, we
michael@0 2791 // always ignore a cached redirect's entity anyway. See bug 759043.
michael@0 2792 int64_t size, contentLength;
michael@0 2793 rv = CheckPartial(entry, &size, &contentLength);
michael@0 2794 NS_ENSURE_SUCCESS(rv,rv);
michael@0 2795
michael@0 2796 if (size == int64_t(-1)) {
michael@0 2797 LOG((" write is in progress"));
michael@0 2798 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
michael@0 2799 LOG((" not interested in the entry, "
michael@0 2800 "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
michael@0 2801
michael@0 2802 *aResult = ENTRY_NOT_WANTED;
michael@0 2803 return NS_OK;
michael@0 2804 }
michael@0 2805
michael@0 2806 // Ignore !(size > 0) from the resumability condition
michael@0 2807 if (!IsResumable(size, contentLength, true)) {
michael@0 2808 LOG((" wait for entry completion, "
michael@0 2809 "response is not resumable"));
michael@0 2810
michael@0 2811 wantCompleteEntry = true;
michael@0 2812 }
michael@0 2813 else {
michael@0 2814 mConcurentCacheAccess = 1;
michael@0 2815 }
michael@0 2816 }
michael@0 2817 else if (contentLength != int64_t(-1) && contentLength != size) {
michael@0 2818 LOG(("Cached data size does not match the Content-Length header "
michael@0 2819 "[content-length=%lld size=%lld]\n", contentLength, size));
michael@0 2820
michael@0 2821 rv = MaybeSetupByteRangeRequest(size, contentLength);
michael@0 2822 mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
michael@0 2823 if (mCachedContentIsPartial) {
michael@0 2824 rv = OpenCacheInputStream(entry, false);
michael@0 2825 *aResult = ENTRY_NEEDS_REVALIDATION;
michael@0 2826 }
michael@0 2827 return rv;
michael@0 2828 }
michael@0 2829 }
michael@0 2830
michael@0 2831 bool usingSSL = false;
michael@0 2832 rv = mURI->SchemeIs("https", &usingSSL);
michael@0 2833 NS_ENSURE_SUCCESS(rv,rv);
michael@0 2834
michael@0 2835 bool doValidation = false;
michael@0 2836 bool canAddImsHeader = true;
michael@0 2837
michael@0 2838 // Cached entry is not the entity we request (see bug #633743)
michael@0 2839 if (ResponseWouldVary(entry)) {
michael@0 2840 LOG(("Validating based on Vary headers returning TRUE\n"));
michael@0 2841 canAddImsHeader = false;
michael@0 2842 doValidation = true;
michael@0 2843 }
michael@0 2844 // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
michael@0 2845 else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE) {
michael@0 2846 LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
michael@0 2847 doValidation = false;
michael@0 2848 }
michael@0 2849 // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
michael@0 2850 // it's revalidated with the server.
michael@0 2851 else if (mLoadFlags & nsIRequest::VALIDATE_ALWAYS) {
michael@0 2852 LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
michael@0 2853 doValidation = true;
michael@0 2854 }
michael@0 2855 // Even if the VALIDATE_NEVER flag is set, there are still some cases in
michael@0 2856 // which we must validate the cached response with the server.
michael@0 2857 else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
michael@0 2858 LOG(("VALIDATE_NEVER set\n"));
michael@0 2859 // if no-store or if no-cache and ssl, validate cached response (see
michael@0 2860 // bug 112564 for an explanation of this logic)
michael@0 2861 if (mCachedResponseHead->NoStore() ||
michael@0 2862 (mCachedResponseHead->NoCache() && usingSSL)) {
michael@0 2863 LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
michael@0 2864 doValidation = true;
michael@0 2865 }
michael@0 2866 else {
michael@0 2867 LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
michael@0 2868 doValidation = false;
michael@0 2869 }
michael@0 2870 }
michael@0 2871 // check if validation is strictly required...
michael@0 2872 else if (mCachedResponseHead->MustValidate()) {
michael@0 2873 LOG(("Validating based on MustValidate() returning TRUE\n"));
michael@0 2874 doValidation = true;
michael@0 2875 }
michael@0 2876 else if (MustValidateBasedOnQueryUrl()) {
michael@0 2877 LOG(("Validating based on RFC 2616 section 13.9 "
michael@0 2878 "(query-url w/o explicit expiration-time)\n"));
michael@0 2879 doValidation = true;
michael@0 2880 }
michael@0 2881 // Check if the cache entry has expired...
michael@0 2882 else {
michael@0 2883 uint32_t time = 0; // a temporary variable for storing time values...
michael@0 2884
michael@0 2885 rv = entry->GetExpirationTime(&time);
michael@0 2886 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2887
michael@0 2888 LOG((" NowInSeconds()=%u, time=%u", NowInSeconds(), time));
michael@0 2889 if (NowInSeconds() <= time)
michael@0 2890 doValidation = false;
michael@0 2891 else if (mCachedResponseHead->MustValidateIfExpired())
michael@0 2892 doValidation = true;
michael@0 2893 else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
michael@0 2894 // If the cached response does not include expiration infor-
michael@0 2895 // mation, then we must validate the response, despite whether
michael@0 2896 // or not this is the first access this session. This behavior
michael@0 2897 // is consistent with existing browsers and is generally expected
michael@0 2898 // by web authors.
michael@0 2899 rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
michael@0 2900 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2901
michael@0 2902 if (time == 0)
michael@0 2903 doValidation = true;
michael@0 2904 else
michael@0 2905 doValidation = fromPreviousSession;
michael@0 2906 }
michael@0 2907 else
michael@0 2908 doValidation = true;
michael@0 2909
michael@0 2910 LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
michael@0 2911 }
michael@0 2912
michael@0 2913 if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) &&
michael@0 2914 (methodWasGet || methodWasHead)) {
michael@0 2915 const char *requestedETag, *cachedETag;
michael@0 2916 cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag);
michael@0 2917 requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match);
michael@0 2918 if (cachedETag && (!strncmp(cachedETag, "W/", 2) ||
michael@0 2919 strcmp(requestedETag, cachedETag))) {
michael@0 2920 // User has defined If-Match header, if the cached entry is not
michael@0 2921 // matching the provided header value or the cached ETag is weak,
michael@0 2922 // force validation.
michael@0 2923 doValidation = true;
michael@0 2924 }
michael@0 2925 }
michael@0 2926
michael@0 2927 if (!doValidation) {
michael@0 2928 //
michael@0 2929 // Check the authorization headers used to generate the cache entry.
michael@0 2930 // We must validate the cache entry if:
michael@0 2931 //
michael@0 2932 // 1) the cache entry was generated prior to this session w/
michael@0 2933 // credentials (see bug 103402).
michael@0 2934 // 2) the cache entry was generated w/o credentials, but would now
michael@0 2935 // require credentials (see bug 96705).
michael@0 2936 //
michael@0 2937 // NOTE: this does not apply to proxy authentication.
michael@0 2938 //
michael@0 2939 entry->GetMetaDataElement("auth", getter_Copies(buf));
michael@0 2940 doValidation =
michael@0 2941 (fromPreviousSession && !buf.IsEmpty()) ||
michael@0 2942 (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
michael@0 2943 }
michael@0 2944
michael@0 2945 // Bug #561276: We maintain a chain of cache-keys which returns cached
michael@0 2946 // 3xx-responses (redirects) in order to detect cycles. If a cycle is
michael@0 2947 // found, ignore the cached response and hit the net. Otherwise, use
michael@0 2948 // the cached response and add the cache-key to the chain. Note that
michael@0 2949 // a limited number of redirects (cached or not) is allowed and is
michael@0 2950 // enforced independently of this mechanism
michael@0 2951 if (!doValidation && isCachedRedirect) {
michael@0 2952 nsAutoCString cacheKey;
michael@0 2953 GenerateCacheKey(mPostID, cacheKey);
michael@0 2954
michael@0 2955 if (!mRedirectedCachekeys)
michael@0 2956 mRedirectedCachekeys = new nsTArray<nsCString>();
michael@0 2957 else if (mRedirectedCachekeys->Contains(cacheKey))
michael@0 2958 doValidation = true;
michael@0 2959
michael@0 2960 LOG(("Redirection-chain %s key %s\n",
michael@0 2961 doValidation ? "contains" : "does not contain", cacheKey.get()));
michael@0 2962
michael@0 2963 // Append cacheKey if not in the chain already
michael@0 2964 if (!doValidation)
michael@0 2965 mRedirectedCachekeys->AppendElement(cacheKey);
michael@0 2966 }
michael@0 2967
michael@0 2968 mCachedContentIsValid = !doValidation;
michael@0 2969
michael@0 2970 if (doValidation) {
michael@0 2971 //
michael@0 2972 // now, we are definitely going to issue a HTTP request to the server.
michael@0 2973 // make it conditional if possible.
michael@0 2974 //
michael@0 2975 // do not attempt to validate no-store content, since servers will not
michael@0 2976 // expect it to be cached. (we only keep it in our cache for the
michael@0 2977 // purposes of back/forward, etc.)
michael@0 2978 //
michael@0 2979 // the request method MUST be either GET or HEAD (see bug 175641).
michael@0 2980 //
michael@0 2981 // do not override conditional headers when consumer has defined its own
michael@0 2982 if (!mCachedResponseHead->NoStore() &&
michael@0 2983 (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
michael@0 2984 !mCustomConditionalRequest) {
michael@0 2985
michael@0 2986 if (mConcurentCacheAccess) {
michael@0 2987 // In case of concurrent read and also validation request we
michael@0 2988 // must wait for the current writer to close the output stream
michael@0 2989 // first. Otherwise, when the writer's job would have been interrupted
michael@0 2990 // before all the data were downloaded, we'd have to do a range request
michael@0 2991 // which would be a second request in line during this channel's
michael@0 2992 // life-time. nsHttpChannel is not designed to do that, so rather
michael@0 2993 // turn off concurrent read and wait for entry's completion.
michael@0 2994 // Then only re-validation or range-re-validation request will go out.
michael@0 2995 mConcurentCacheAccess = 0;
michael@0 2996 // This will cause that OnCacheEntryCheck is called again with the same
michael@0 2997 // entry after the writer is done.
michael@0 2998 wantCompleteEntry = true;
michael@0 2999 } else {
michael@0 3000 const char *val;
michael@0 3001 // Add If-Modified-Since header if a Last-Modified was given
michael@0 3002 // and we are allowed to do this (see bugs 510359 and 269303)
michael@0 3003 if (canAddImsHeader) {
michael@0 3004 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
michael@0 3005 if (val)
michael@0 3006 mRequestHead.SetHeader(nsHttp::If_Modified_Since,
michael@0 3007 nsDependentCString(val));
michael@0 3008 }
michael@0 3009 // Add If-None-Match header if an ETag was given in the response
michael@0 3010 val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
michael@0 3011 if (val)
michael@0 3012 mRequestHead.SetHeader(nsHttp::If_None_Match,
michael@0 3013 nsDependentCString(val));
michael@0 3014 mDidReval = true;
michael@0 3015 }
michael@0 3016 }
michael@0 3017 }
michael@0 3018
michael@0 3019 if (mCachedContentIsValid || mDidReval) {
michael@0 3020 rv = OpenCacheInputStream(entry, mCachedContentIsValid);
michael@0 3021 if (NS_FAILED(rv)) {
michael@0 3022 // If we can't get the entity then we have to act as though we
michael@0 3023 // don't have the cache entry.
michael@0 3024 if (mDidReval) {
michael@0 3025 // Make the request unconditional again.
michael@0 3026 mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
michael@0 3027 mRequestHead.ClearHeader(nsHttp::If_None_Match);
michael@0 3028 mRequestHead.ClearHeader(nsHttp::ETag);
michael@0 3029 mDidReval = false;
michael@0 3030 }
michael@0 3031 mCachedContentIsValid = false;
michael@0 3032 }
michael@0 3033 }
michael@0 3034
michael@0 3035 if (mDidReval)
michael@0 3036 *aResult = ENTRY_NEEDS_REVALIDATION;
michael@0 3037 else if (wantCompleteEntry)
michael@0 3038 *aResult = RECHECK_AFTER_WRITE_FINISHED;
michael@0 3039 else
michael@0 3040 *aResult = ENTRY_WANTED;
michael@0 3041
michael@0 3042 if (mCachedContentIsValid) {
michael@0 3043 entry->MaybeMarkValid();
michael@0 3044 }
michael@0 3045
michael@0 3046 LOG(("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d result=%d]\n",
michael@0 3047 this, doValidation, *aResult));
michael@0 3048 return rv;
michael@0 3049 }
michael@0 3050
michael@0 3051 NS_IMETHODIMP
michael@0 3052 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry,
michael@0 3053 bool aNew,
michael@0 3054 nsIApplicationCache* aAppCache,
michael@0 3055 nsresult status)
michael@0 3056 {
michael@0 3057 MOZ_ASSERT(NS_IsMainThread());
michael@0 3058
michael@0 3059 nsresult rv;
michael@0 3060
michael@0 3061 LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
michael@0 3062 "new=%d appcache=%p status=%x]\n", this, entry, aNew, aAppCache, status));
michael@0 3063
michael@0 3064 // if the channel's already fired onStopRequest, then we should ignore
michael@0 3065 // this event.
michael@0 3066 if (!mIsPending) {
michael@0 3067 mCacheInputStream.CloseAndRelease();
michael@0 3068 return NS_OK;
michael@0 3069 }
michael@0 3070
michael@0 3071 rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
michael@0 3072 if (NS_FAILED(rv)) {
michael@0 3073 CloseCacheEntry(true);
michael@0 3074 AsyncAbort(rv);
michael@0 3075 }
michael@0 3076
michael@0 3077 return NS_OK;
michael@0 3078 }
michael@0 3079
michael@0 3080 nsresult
michael@0 3081 nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry *entry,
michael@0 3082 bool aNew,
michael@0 3083 nsIApplicationCache* aAppCache,
michael@0 3084 nsresult status)
michael@0 3085 {
michael@0 3086 nsresult rv;
michael@0 3087
michael@0 3088 if (mCanceled) {
michael@0 3089 LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
michael@0 3090 return mStatus;
michael@0 3091 }
michael@0 3092
michael@0 3093 if (aAppCache) {
michael@0 3094 if (mApplicationCache == aAppCache && !mCacheEntry) {
michael@0 3095 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
michael@0 3096 }
michael@0 3097 else if (mApplicationCacheForWrite == aAppCache && aNew && !mOfflineCacheEntry) {
michael@0 3098 rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
michael@0 3099 }
michael@0 3100 else {
michael@0 3101 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
michael@0 3102 }
michael@0 3103 }
michael@0 3104 else {
michael@0 3105 rv = OnNormalCacheEntryAvailable(entry, aNew, status);
michael@0 3106 }
michael@0 3107
michael@0 3108 if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
michael@0 3109 // If we have a fallback URI (and we're not already
michael@0 3110 // falling back), process the fallback asynchronously.
michael@0 3111 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
michael@0 3112 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
michael@0 3113 }
michael@0 3114 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 3115 }
michael@0 3116
michael@0 3117 if (NS_FAILED(rv))
michael@0 3118 return rv;
michael@0 3119
michael@0 3120 // We may be waiting for more callbacks...
michael@0 3121 if (mCacheEntriesToWaitFor)
michael@0 3122 return NS_OK;
michael@0 3123
michael@0 3124 return ContinueConnect();
michael@0 3125 }
michael@0 3126
michael@0 3127 nsresult
michael@0 3128 nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
michael@0 3129 bool aNew,
michael@0 3130 nsresult aEntryStatus)
michael@0 3131 {
michael@0 3132 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
michael@0 3133
michael@0 3134 if (NS_FAILED(aEntryStatus) || aNew) {
michael@0 3135 // Make sure this flag is dropped. It may happen the entry is doomed
michael@0 3136 // between OnCacheEntryCheck and OnCacheEntryAvailable.
michael@0 3137 mCachedContentIsValid = false;
michael@0 3138
michael@0 3139 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
michael@0 3140 // if this channel is only allowed to pull from the cache, then
michael@0 3141 // we must fail if we were unable to open a cache entry for read.
michael@0 3142 return NS_ERROR_DOCUMENT_NOT_CACHED;
michael@0 3143 }
michael@0 3144 }
michael@0 3145
michael@0 3146 if (NS_SUCCEEDED(aEntryStatus)) {
michael@0 3147 mCacheEntry = aEntry;
michael@0 3148 mCacheEntryIsWriteOnly = aNew;
michael@0 3149
michael@0 3150 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
michael@0 3151 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
michael@0 3152 false);
michael@0 3153 }
michael@0 3154 }
michael@0 3155
michael@0 3156 return NS_OK;
michael@0 3157 }
michael@0 3158
michael@0 3159 nsresult
michael@0 3160 nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry,
michael@0 3161 bool aNew,
michael@0 3162 nsIApplicationCache* aAppCache,
michael@0 3163 nsresult aEntryStatus)
michael@0 3164 {
michael@0 3165 MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
michael@0 3166 MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
michael@0 3167
michael@0 3168 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
michael@0 3169
michael@0 3170 nsresult rv;
michael@0 3171
michael@0 3172 if (!mApplicationCache)
michael@0 3173 mApplicationCache = aAppCache;
michael@0 3174
michael@0 3175 if (NS_SUCCEEDED(aEntryStatus)) {
michael@0 3176 // We successfully opened an offline cache session and the entry,
michael@0 3177 // so indicate we will load from the offline cache.
michael@0 3178 mLoadedFromApplicationCache = true;
michael@0 3179 mCacheEntryIsReadOnly = true;
michael@0 3180 mCacheEntry = aEntry;
michael@0 3181 mCacheEntryIsWriteOnly = false;
michael@0 3182
michael@0 3183 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
michael@0 3184 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
michael@0 3185 true);
michael@0 3186 }
michael@0 3187
michael@0 3188 return NS_OK;
michael@0 3189 }
michael@0 3190
michael@0 3191 if (!mApplicationCacheForWrite && !mFallbackChannel) {
michael@0 3192 // Check for namespace match.
michael@0 3193 nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
michael@0 3194 rv = mApplicationCache->GetMatchingNamespace(mSpec,
michael@0 3195 getter_AddRefs(namespaceEntry));
michael@0 3196 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3197
michael@0 3198 uint32_t namespaceType = 0;
michael@0 3199 if (!namespaceEntry ||
michael@0 3200 NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
michael@0 3201 (namespaceType &
michael@0 3202 (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
michael@0 3203 nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
michael@0 3204 // When loading from an application cache, only items
michael@0 3205 // on the whitelist or matching a
michael@0 3206 // fallback namespace should hit the network...
michael@0 3207 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
michael@0 3208
michael@0 3209 // ... and if there were an application cache entry,
michael@0 3210 // we would have found it earlier.
michael@0 3211 return NS_ERROR_CACHE_KEY_NOT_FOUND;
michael@0 3212 }
michael@0 3213
michael@0 3214 if (namespaceType &
michael@0 3215 nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
michael@0 3216 rv = namespaceEntry->GetData(mFallbackKey);
michael@0 3217 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3218 }
michael@0 3219 }
michael@0 3220
michael@0 3221 return NS_OK;
michael@0 3222 }
michael@0 3223
michael@0 3224 nsresult
michael@0 3225 nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
michael@0 3226 nsIApplicationCache* aAppCache,
michael@0 3227 nsresult aEntryStatus)
michael@0 3228 {
michael@0 3229 MOZ_ASSERT(mApplicationCacheForWrite && aAppCache == mApplicationCacheForWrite);
michael@0 3230
michael@0 3231 mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
michael@0 3232
michael@0 3233 if (NS_SUCCEEDED(aEntryStatus)) {
michael@0 3234 mOfflineCacheEntry = aEntry;
michael@0 3235 if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
michael@0 3236 mOfflineCacheLastModifiedTime = 0;
michael@0 3237 }
michael@0 3238 }
michael@0 3239
michael@0 3240 return aEntryStatus;
michael@0 3241 }
michael@0 3242
michael@0 3243 // Generates the proper cache-key for this instance of nsHttpChannel
michael@0 3244 nsresult
michael@0 3245 nsHttpChannel::GenerateCacheKey(uint32_t postID, nsACString &cacheKey)
michael@0 3246 {
michael@0 3247 AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(),
michael@0 3248 postID, cacheKey);
michael@0 3249 return NS_OK;
michael@0 3250 }
michael@0 3251
michael@0 3252 // Assembles a cache-key from the given pieces of information and |mLoadFlags|
michael@0 3253 void
michael@0 3254 nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID,
michael@0 3255 nsACString &cacheKey)
michael@0 3256 {
michael@0 3257 cacheKey.Truncate();
michael@0 3258
michael@0 3259 if (mLoadFlags & LOAD_ANONYMOUS) {
michael@0 3260 cacheKey.AssignLiteral("anon&");
michael@0 3261 }
michael@0 3262
michael@0 3263 if (postID) {
michael@0 3264 char buf[32];
michael@0 3265 PR_snprintf(buf, sizeof(buf), "id=%x&", postID);
michael@0 3266 cacheKey.Append(buf);
michael@0 3267 }
michael@0 3268
michael@0 3269 if (strlen(mCacheDomain.get()) > 0) {
michael@0 3270 cacheKey.AppendLiteral("domain=");
michael@0 3271 cacheKey.Append(mCacheDomain.get());
michael@0 3272 cacheKey.AppendLiteral("&");
michael@0 3273 }
michael@0 3274
michael@0 3275 if (!cacheKey.IsEmpty()) {
michael@0 3276 cacheKey.AppendLiteral("uri=");
michael@0 3277 }
michael@0 3278
michael@0 3279 // Strip any trailing #ref from the URL before using it as the key
michael@0 3280 const char *p = strchr(spec, '#');
michael@0 3281 if (p)
michael@0 3282 cacheKey.Append(spec, p - spec);
michael@0 3283 else
michael@0 3284 cacheKey.Append(spec);
michael@0 3285 }
michael@0 3286
michael@0 3287 // UpdateExpirationTime is called when a new response comes in from the server.
michael@0 3288 // It updates the stored response-time and sets the expiration time on the
michael@0 3289 // cache entry.
michael@0 3290 //
michael@0 3291 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
michael@0 3292 //
michael@0 3293 // timeRemaining = freshnessLifetime - currentAge
michael@0 3294 // expirationTime = now + timeRemaining
michael@0 3295 //
michael@0 3296 nsresult
michael@0 3297 nsHttpChannel::UpdateExpirationTime()
michael@0 3298 {
michael@0 3299 NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
michael@0 3300
michael@0 3301 nsresult rv;
michael@0 3302
michael@0 3303 uint32_t expirationTime = 0;
michael@0 3304 if (!mResponseHead->MustValidate()) {
michael@0 3305 uint32_t freshnessLifetime = 0;
michael@0 3306
michael@0 3307 rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
michael@0 3308 if (NS_FAILED(rv)) return rv;
michael@0 3309
michael@0 3310 if (freshnessLifetime > 0) {
michael@0 3311 uint32_t now = NowInSeconds(), currentAge = 0;
michael@0 3312
michael@0 3313 rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
michael@0 3314 if (NS_FAILED(rv)) return rv;
michael@0 3315
michael@0 3316 LOG(("freshnessLifetime = %u, currentAge = %u\n",
michael@0 3317 freshnessLifetime, currentAge));
michael@0 3318
michael@0 3319 if (freshnessLifetime > currentAge) {
michael@0 3320 uint32_t timeRemaining = freshnessLifetime - currentAge;
michael@0 3321 // be careful... now + timeRemaining may overflow
michael@0 3322 if (now + timeRemaining < now)
michael@0 3323 expirationTime = uint32_t(-1);
michael@0 3324 else
michael@0 3325 expirationTime = now + timeRemaining;
michael@0 3326 }
michael@0 3327 else
michael@0 3328 expirationTime = now;
michael@0 3329 }
michael@0 3330 }
michael@0 3331
michael@0 3332 rv = mCacheEntry->SetExpirationTime(expirationTime);
michael@0 3333 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3334
michael@0 3335 if (mOfflineCacheEntry) {
michael@0 3336 rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
michael@0 3337 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3338 }
michael@0 3339
michael@0 3340 return NS_OK;
michael@0 3341 }
michael@0 3342
michael@0 3343 /*static*/ inline bool
michael@0 3344 nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
michael@0 3345 {
michael@0 3346 // Must be called on the main thread because nsIURI does not implement
michael@0 3347 // thread-safe QueryInterface.
michael@0 3348 MOZ_ASSERT(NS_IsMainThread());
michael@0 3349
michael@0 3350 if (method != nsHttpRequestHead::kMethod_Get &&
michael@0 3351 method != nsHttpRequestHead::kMethod_Head)
michael@0 3352 return false;
michael@0 3353
michael@0 3354 nsAutoCString query;
michael@0 3355 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
michael@0 3356 nsresult rv = url->GetQuery(query);
michael@0 3357 return NS_SUCCEEDED(rv) && !query.IsEmpty();
michael@0 3358 }
michael@0 3359
michael@0 3360 bool
michael@0 3361 nsHttpChannel::MustValidateBasedOnQueryUrl() const
michael@0 3362 {
michael@0 3363 // RFC 2616, section 13.9 states that GET-requests with a query-url
michael@0 3364 // MUST NOT be treated as fresh unless the server explicitly provides
michael@0 3365 // an expiration-time in the response. See bug #468594
michael@0 3366 // Section 13.2.1 (6th paragraph) defines "explicit expiration time"
michael@0 3367 if (mHasQueryString)
michael@0 3368 {
michael@0 3369 uint32_t tmp; // we don't need the value, just whether it's set
michael@0 3370 nsresult rv = mCachedResponseHead->GetExpiresValue(&tmp);
michael@0 3371 if (NS_FAILED(rv)) {
michael@0 3372 rv = mCachedResponseHead->GetMaxAgeValue(&tmp);
michael@0 3373 if (NS_FAILED(rv)) {
michael@0 3374 return true;
michael@0 3375 }
michael@0 3376 }
michael@0 3377 }
michael@0 3378 return false;
michael@0 3379 }
michael@0 3380
michael@0 3381
michael@0 3382 bool
michael@0 3383 nsHttpChannel::ShouldUpdateOfflineCacheEntry()
michael@0 3384 {
michael@0 3385 if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
michael@0 3386 return false;
michael@0 3387 }
michael@0 3388
michael@0 3389 // if we're updating the cache entry, update the offline cache entry too
michael@0 3390 if (mCacheEntry && mCacheEntryIsWriteOnly) {
michael@0 3391 return true;
michael@0 3392 }
michael@0 3393
michael@0 3394 // if there's nothing in the offline cache, add it
michael@0 3395 if (mOfflineCacheEntry) {
michael@0 3396 return true;
michael@0 3397 }
michael@0 3398
michael@0 3399 // if the document is newer than the offline entry, update it
michael@0 3400 uint32_t docLastModifiedTime;
michael@0 3401 nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
michael@0 3402 if (NS_FAILED(rv)) {
michael@0 3403 return true;
michael@0 3404 }
michael@0 3405
michael@0 3406 if (mOfflineCacheLastModifiedTime == 0) {
michael@0 3407 return false;
michael@0 3408 }
michael@0 3409
michael@0 3410 if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
michael@0 3411 return true;
michael@0 3412 }
michael@0 3413
michael@0 3414 return false;
michael@0 3415 }
michael@0 3416
michael@0 3417 nsresult
michael@0 3418 nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering)
michael@0 3419 {
michael@0 3420 nsresult rv;
michael@0 3421
michael@0 3422 bool usingSSL = false;
michael@0 3423 rv = mURI->SchemeIs("https", &usingSSL);
michael@0 3424 NS_ENSURE_SUCCESS(rv,rv);
michael@0 3425
michael@0 3426 if (usingSSL) {
michael@0 3427 rv = cacheEntry->GetSecurityInfo(
michael@0 3428 getter_AddRefs(mCachedSecurityInfo));
michael@0 3429 if (NS_FAILED(rv)) {
michael@0 3430 LOG(("failed to parse security-info [channel=%p, entry=%p]",
michael@0 3431 this, cacheEntry));
michael@0 3432 NS_WARNING("failed to parse security-info");
michael@0 3433 return rv;
michael@0 3434 }
michael@0 3435
michael@0 3436 // XXX: We should not be skilling this check in the offline cache
michael@0 3437 // case, but we have to do so now to work around bug 794507.
michael@0 3438 MOZ_ASSERT(mCachedSecurityInfo || mLoadedFromApplicationCache);
michael@0 3439 if (!mCachedSecurityInfo && !mLoadedFromApplicationCache) {
michael@0 3440 LOG(("mCacheEntry->GetSecurityInfo returned success but did not "
michael@0 3441 "return the security info [channel=%p, entry=%p]",
michael@0 3442 this, cacheEntry));
michael@0 3443 return NS_ERROR_UNEXPECTED; // XXX error code
michael@0 3444 }
michael@0 3445 }
michael@0 3446
michael@0 3447 // Keep the conditions below in sync with the conditions in ReadFromCache.
michael@0 3448
michael@0 3449 rv = NS_OK;
michael@0 3450
michael@0 3451 if (WillRedirect(mCachedResponseHead)) {
michael@0 3452 // Do not even try to read the entity for a redirect because we do not
michael@0 3453 // return an entity to the application when we process redirects.
michael@0 3454 LOG(("Will skip read of cached redirect entity\n"));
michael@0 3455 return NS_OK;
michael@0 3456 }
michael@0 3457
michael@0 3458 if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
michael@0 3459 !mCachedContentIsPartial) {
michael@0 3460 // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
michael@0 3461 // cached entity.
michael@0 3462 if (!mApplicationCacheForWrite) {
michael@0 3463 LOG(("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
michael@0 3464 "load flag\n"));
michael@0 3465 return NS_OK;
michael@0 3466 }
michael@0 3467
michael@0 3468 // If offline caching has been requested and the offline cache needs
michael@0 3469 // updating, we must complete the call even if the main cache entry
michael@0 3470 // is up to date. We don't know yet for sure whether the offline
michael@0 3471 // cache needs updating because at this point we haven't opened it
michael@0 3472 // for writing yet, so we have to start reading the cached entity now
michael@0 3473 // just in case.
michael@0 3474 LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
michael@0 3475 "load flag\n"));
michael@0 3476 }
michael@0 3477
michael@0 3478 // Open an input stream for the entity, so that the call to OpenInputStream
michael@0 3479 // happens off the main thread.
michael@0 3480 nsCOMPtr<nsIInputStream> stream;
michael@0 3481 rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
michael@0 3482
michael@0 3483 if (NS_FAILED(rv)) {
michael@0 3484 LOG(("Failed to open cache input stream [channel=%p, "
michael@0 3485 "mCacheEntry=%p]", this, cacheEntry));
michael@0 3486 return rv;
michael@0 3487 }
michael@0 3488
michael@0 3489 if (startBuffering) {
michael@0 3490 bool nonBlocking;
michael@0 3491 rv = stream->IsNonBlocking(&nonBlocking);
michael@0 3492 if (NS_SUCCEEDED(rv) && nonBlocking)
michael@0 3493 startBuffering = false;
michael@0 3494 }
michael@0 3495
michael@0 3496 if (!startBuffering) {
michael@0 3497 // Bypass wrapping the input stream for the new cache back-end since
michael@0 3498 // nsIStreamTransportService expects a blocking stream. Preloading of
michael@0 3499 // the data must be done on the level of the cache backend, internally.
michael@0 3500 //
michael@0 3501 // We do not connect the stream to the stream transport service if we
michael@0 3502 // have to validate the entry with the server. If we did, we would get
michael@0 3503 // into a race condition between the stream transport service reading
michael@0 3504 // the existing contents and the opening of the cache entry's output
michael@0 3505 // stream to write the new contents in the case where we get a non-304
michael@0 3506 // response.
michael@0 3507 LOG(("Opened cache input stream without buffering [channel=%p, "
michael@0 3508 "mCacheEntry=%p, stream=%p]", this,
michael@0 3509 cacheEntry, stream.get()));
michael@0 3510 mCacheInputStream.takeOver(stream);
michael@0 3511 return rv;
michael@0 3512 }
michael@0 3513
michael@0 3514 // Have the stream transport service start reading the entity on one of its
michael@0 3515 // background threads.
michael@0 3516
michael@0 3517 nsCOMPtr<nsITransport> transport;
michael@0 3518 nsCOMPtr<nsIInputStream> wrapper;
michael@0 3519
michael@0 3520 nsCOMPtr<nsIStreamTransportService> sts =
michael@0 3521 do_GetService(kStreamTransportServiceCID, &rv);
michael@0 3522 if (NS_SUCCEEDED(rv)) {
michael@0 3523 rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
michael@0 3524 true, getter_AddRefs(transport));
michael@0 3525 }
michael@0 3526 if (NS_SUCCEEDED(rv)) {
michael@0 3527 rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
michael@0 3528 }
michael@0 3529 if (NS_SUCCEEDED(rv)) {
michael@0 3530 LOG(("Opened cache input stream [channel=%p, wrapper=%p, "
michael@0 3531 "transport=%p, stream=%p]", this, wrapper.get(),
michael@0 3532 transport.get(), stream.get()));
michael@0 3533 } else {
michael@0 3534 LOG(("Failed to open cache input stream [channel=%p, "
michael@0 3535 "wrapper=%p, transport=%p, stream=%p]", this,
michael@0 3536 wrapper.get(), transport.get(), stream.get()));
michael@0 3537
michael@0 3538 stream->Close();
michael@0 3539 return rv;
michael@0 3540 }
michael@0 3541
michael@0 3542 mCacheInputStream.takeOver(wrapper);
michael@0 3543
michael@0 3544 return NS_OK;
michael@0 3545 }
michael@0 3546
michael@0 3547 // Actually process the cached response that we started to handle in CheckCache
michael@0 3548 // and/or StartBufferingCachedEntity.
michael@0 3549 nsresult
michael@0 3550 nsHttpChannel::ReadFromCache(bool alreadyMarkedValid)
michael@0 3551 {
michael@0 3552 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
michael@0 3553 NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
michael@0 3554
michael@0 3555 LOG(("nsHttpChannel::ReadFromCache [this=%p] "
michael@0 3556 "Using cached copy of: %s\n", this, mSpec.get()));
michael@0 3557
michael@0 3558 if (mCachedResponseHead)
michael@0 3559 mResponseHead = mCachedResponseHead;
michael@0 3560
michael@0 3561 UpdateInhibitPersistentCachingFlag();
michael@0 3562
michael@0 3563 // if we don't already have security info, try to get it from the cache
michael@0 3564 // entry. there are two cases to consider here: 1) we are just reading
michael@0 3565 // from the cache, or 2) this may be due to a 304 not modified response,
michael@0 3566 // in which case we could have security info from a socket transport.
michael@0 3567 if (!mSecurityInfo)
michael@0 3568 mSecurityInfo = mCachedSecurityInfo;
michael@0 3569
michael@0 3570 if (!alreadyMarkedValid && !mCachedContentIsPartial) {
michael@0 3571 // We validated the entry, and we have write access to the cache, so
michael@0 3572 // mark the cache entry as valid in order to allow others access to
michael@0 3573 // this cache entry.
michael@0 3574 //
michael@0 3575 // TODO: This should be done asynchronously so we don't take the cache
michael@0 3576 // service lock on the main thread.
michael@0 3577 mCacheEntry->MaybeMarkValid();
michael@0 3578 }
michael@0 3579
michael@0 3580 nsresult rv;
michael@0 3581
michael@0 3582 // Keep the conditions below in sync with the conditions in
michael@0 3583 // StartBufferingCachedEntity.
michael@0 3584
michael@0 3585 if (WillRedirect(mResponseHead)) {
michael@0 3586 // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
michael@0 3587 // to avoid event dispatching latency.
michael@0 3588 MOZ_ASSERT(!mCacheInputStream);
michael@0 3589 LOG(("Skipping skip read of cached redirect entity\n"));
michael@0 3590 return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
michael@0 3591 }
michael@0 3592
michael@0 3593 if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
michael@0 3594 if (!mApplicationCacheForWrite) {
michael@0 3595 LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
michael@0 3596 "load flag\n"));
michael@0 3597 MOZ_ASSERT(!mCacheInputStream);
michael@0 3598 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
michael@0 3599 // here, to avoid event dispatching latency.
michael@0 3600 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
michael@0 3601 }
michael@0 3602
michael@0 3603 if (!ShouldUpdateOfflineCacheEntry()) {
michael@0 3604 LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
michael@0 3605 "load flag (mApplicationCacheForWrite not null case)\n"));
michael@0 3606 mCacheInputStream.CloseAndRelease();
michael@0 3607 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
michael@0 3608 // here, to avoid event dispatching latency.
michael@0 3609 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
michael@0 3610 }
michael@0 3611 }
michael@0 3612
michael@0 3613 MOZ_ASSERT(mCacheInputStream);
michael@0 3614 if (!mCacheInputStream) {
michael@0 3615 NS_ERROR("mCacheInputStream is null but we're expecting to "
michael@0 3616 "be able to read from it.");
michael@0 3617 return NS_ERROR_UNEXPECTED;
michael@0 3618 }
michael@0 3619
michael@0 3620
michael@0 3621 nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
michael@0 3622
michael@0 3623 rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream,
michael@0 3624 int64_t(-1), int64_t(-1), 0, 0, true);
michael@0 3625 if (NS_FAILED(rv)) {
michael@0 3626 inputStream->Close();
michael@0 3627 return rv;
michael@0 3628 }
michael@0 3629
michael@0 3630 rv = mCachePump->AsyncRead(this, mListenerContext);
michael@0 3631 if (NS_FAILED(rv)) return rv;
michael@0 3632
michael@0 3633 if (mTimingEnabled)
michael@0 3634 mCacheReadStart = TimeStamp::Now();
michael@0 3635
michael@0 3636 uint32_t suspendCount = mSuspendCount;
michael@0 3637 while (suspendCount--)
michael@0 3638 mCachePump->Suspend();
michael@0 3639
michael@0 3640 return NS_OK;
michael@0 3641 }
michael@0 3642
michael@0 3643 void
michael@0 3644 nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
michael@0 3645 {
michael@0 3646 mCacheInputStream.CloseAndRelease();
michael@0 3647
michael@0 3648 if (!mCacheEntry)
michael@0 3649 return;
michael@0 3650
michael@0 3651 LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheEntryIsWriteOnly=%x",
michael@0 3652 this, mStatus, mCacheEntryIsWriteOnly));
michael@0 3653
michael@0 3654 // If we have begun to create or replace a cache entry, and that cache
michael@0 3655 // entry is not complete and not resumable, then it needs to be doomed.
michael@0 3656 // Otherwise, CheckCache will make the mistake of thinking that the
michael@0 3657 // partial cache entry is complete.
michael@0 3658
michael@0 3659 bool doom = false;
michael@0 3660 if (mInitedCacheEntry) {
michael@0 3661 MOZ_ASSERT(mResponseHead, "oops");
michael@0 3662 if (NS_FAILED(mStatus) && doomOnFailure &&
michael@0 3663 mCacheEntryIsWriteOnly && !mResponseHead->IsResumable())
michael@0 3664 doom = true;
michael@0 3665 }
michael@0 3666 else if (mCacheEntryIsWriteOnly)
michael@0 3667 doom = true;
michael@0 3668
michael@0 3669 if (doom) {
michael@0 3670 LOG((" dooming cache entry!!"));
michael@0 3671 mCacheEntry->AsyncDoom(nullptr);
michael@0 3672 }
michael@0 3673
michael@0 3674 mCachedResponseHead = nullptr;
michael@0 3675
michael@0 3676 mCachePump = nullptr;
michael@0 3677 mCacheEntry = nullptr;
michael@0 3678 mCacheEntryIsWriteOnly = false;
michael@0 3679 mInitedCacheEntry = false;
michael@0 3680 }
michael@0 3681
michael@0 3682
michael@0 3683 void
michael@0 3684 nsHttpChannel::CloseOfflineCacheEntry()
michael@0 3685 {
michael@0 3686 if (!mOfflineCacheEntry)
michael@0 3687 return;
michael@0 3688
michael@0 3689 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
michael@0 3690
michael@0 3691 if (NS_FAILED(mStatus)) {
michael@0 3692 mOfflineCacheEntry->AsyncDoom(nullptr);
michael@0 3693 }
michael@0 3694 else {
michael@0 3695 bool succeeded;
michael@0 3696 if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
michael@0 3697 mOfflineCacheEntry->AsyncDoom(nullptr);
michael@0 3698 }
michael@0 3699
michael@0 3700 mOfflineCacheEntry = nullptr;
michael@0 3701 }
michael@0 3702
michael@0 3703
michael@0 3704 // Initialize the cache entry for writing.
michael@0 3705 // - finalize storage policy
michael@0 3706 // - store security info
michael@0 3707 // - update expiration time
michael@0 3708 // - store headers and other meta data
michael@0 3709 nsresult
michael@0 3710 nsHttpChannel::InitCacheEntry()
michael@0 3711 {
michael@0 3712 nsresult rv;
michael@0 3713
michael@0 3714 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
michael@0 3715 // if only reading, nothing to be done here.
michael@0 3716 if (mCacheEntryIsReadOnly)
michael@0 3717 return NS_OK;
michael@0 3718
michael@0 3719 // Don't cache the response again if already cached...
michael@0 3720 if (mCachedContentIsValid)
michael@0 3721 return NS_OK;
michael@0 3722
michael@0 3723 LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n",
michael@0 3724 this, mCacheEntry.get()));
michael@0 3725
michael@0 3726 bool recreate = !mCacheEntryIsWriteOnly;
michael@0 3727 bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
michael@0 3728
michael@0 3729 if (!recreate && dontPersist) {
michael@0 3730 // If the current entry is persistent but we inhibit peristence
michael@0 3731 // then force recreation of the entry as memory/only.
michael@0 3732 rv = mCacheEntry->GetPersistent(&recreate);
michael@0 3733 if (NS_FAILED(rv))
michael@0 3734 return rv;
michael@0 3735 }
michael@0 3736
michael@0 3737 if (recreate) {
michael@0 3738 LOG((" we have a ready entry, but reading it again from the server -> recreating cache entry\n"));
michael@0 3739 nsCOMPtr<nsICacheEntry> currentEntry;
michael@0 3740 currentEntry.swap(mCacheEntry);
michael@0 3741 rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
michael@0 3742 if (NS_FAILED(rv)) {
michael@0 3743 LOG((" recreation failed, the response will not be cached"));
michael@0 3744 return NS_OK;
michael@0 3745 }
michael@0 3746
michael@0 3747 mCacheEntryIsWriteOnly = true;
michael@0 3748 }
michael@0 3749
michael@0 3750 // Set the expiration time for this cache entry
michael@0 3751 rv = UpdateExpirationTime();
michael@0 3752 if (NS_FAILED(rv)) return rv;
michael@0 3753
michael@0 3754 rv = AddCacheEntryHeaders(mCacheEntry);
michael@0 3755 if (NS_FAILED(rv)) return rv;
michael@0 3756
michael@0 3757 mInitedCacheEntry = true;
michael@0 3758
michael@0 3759 // Don't perform the check when writing (doesn't make sense)
michael@0 3760 mConcurentCacheAccess = 0;
michael@0 3761
michael@0 3762 return NS_OK;
michael@0 3763 }
michael@0 3764
michael@0 3765 void
michael@0 3766 nsHttpChannel::UpdateInhibitPersistentCachingFlag()
michael@0 3767 {
michael@0 3768 // The no-store directive within the 'Cache-Control:' header indicates
michael@0 3769 // that we must not store the response in a persistent cache.
michael@0 3770 if (mResponseHead->NoStore())
michael@0 3771 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
michael@0 3772
michael@0 3773 // Only cache SSL content on disk if the pref is set
michael@0 3774 if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
michael@0 3775 mConnectionInfo->UsingSSL())
michael@0 3776 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
michael@0 3777 }
michael@0 3778
michael@0 3779 nsresult
michael@0 3780 nsHttpChannel::InitOfflineCacheEntry()
michael@0 3781 {
michael@0 3782 // This function can be called even when we fail to connect (bug 551990)
michael@0 3783
michael@0 3784 if (!mOfflineCacheEntry) {
michael@0 3785 return NS_OK;
michael@0 3786 }
michael@0 3787
michael@0 3788 if (!mResponseHead || mResponseHead->NoStore()) {
michael@0 3789 if (mResponseHead && mResponseHead->NoStore()) {
michael@0 3790 mOfflineCacheEntry->AsyncDoom(nullptr);
michael@0 3791 }
michael@0 3792
michael@0 3793 CloseOfflineCacheEntry();
michael@0 3794
michael@0 3795 if (mResponseHead && mResponseHead->NoStore()) {
michael@0 3796 return NS_ERROR_NOT_AVAILABLE;
michael@0 3797 }
michael@0 3798
michael@0 3799 return NS_OK;
michael@0 3800 }
michael@0 3801
michael@0 3802 // This entry's expiration time should match the main entry's expiration
michael@0 3803 // time. UpdateExpirationTime() will keep it in sync once the offline
michael@0 3804 // cache entry has been created.
michael@0 3805 if (mCacheEntry) {
michael@0 3806 uint32_t expirationTime;
michael@0 3807 nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
michael@0 3808 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3809
michael@0 3810 mOfflineCacheEntry->SetExpirationTime(expirationTime);
michael@0 3811 }
michael@0 3812
michael@0 3813 return AddCacheEntryHeaders(mOfflineCacheEntry);
michael@0 3814 }
michael@0 3815
michael@0 3816
michael@0 3817 nsresult
michael@0 3818 nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry)
michael@0 3819 {
michael@0 3820 nsresult rv;
michael@0 3821
michael@0 3822 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", this));
michael@0 3823 // Store secure data in memory only
michael@0 3824 if (mSecurityInfo)
michael@0 3825 entry->SetSecurityInfo(mSecurityInfo);
michael@0 3826
michael@0 3827 // Store the HTTP request method with the cache entry so we can distinguish
michael@0 3828 // for example GET and HEAD responses.
michael@0 3829 rv = entry->SetMetaDataElement("request-method",
michael@0 3830 mRequestHead.Method().get());
michael@0 3831 if (NS_FAILED(rv)) return rv;
michael@0 3832
michael@0 3833 // Store the HTTP authorization scheme used if any...
michael@0 3834 rv = StoreAuthorizationMetaData(entry);
michael@0 3835 if (NS_FAILED(rv)) return rv;
michael@0 3836
michael@0 3837 // Iterate over the headers listed in the Vary response header, and
michael@0 3838 // store the value of the corresponding request header so we can verify
michael@0 3839 // that it has not varied when we try to re-use the cached response at
michael@0 3840 // a later time. Take care to store "Cookie" headers only as hashes
michael@0 3841 // due to security considerations and the fact that they can be pretty
michael@0 3842 // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
michael@0 3843 //
michael@0 3844 // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
michael@0 3845 // in the cache. we could try to avoid needlessly storing the "accept"
michael@0 3846 // header in this case, but it doesn't seem worth the extra code to perform
michael@0 3847 // the check.
michael@0 3848 {
michael@0 3849 nsAutoCString buf, metaKey;
michael@0 3850 mResponseHead->GetHeader(nsHttp::Vary, buf);
michael@0 3851 if (!buf.IsEmpty()) {
michael@0 3852 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
michael@0 3853
michael@0 3854 char *val = buf.BeginWriting(); // going to munge buf
michael@0 3855 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
michael@0 3856 while (token) {
michael@0 3857 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
michael@0 3858 "processing %s", this, token));
michael@0 3859 if (*token != '*') {
michael@0 3860 nsHttpAtom atom = nsHttp::ResolveAtom(token);
michael@0 3861 const char *val = mRequestHead.PeekHeader(atom);
michael@0 3862 nsAutoCString hash;
michael@0 3863 if (val) {
michael@0 3864 // If cookie-header, store a hash of the value
michael@0 3865 if (atom == nsHttp::Cookie) {
michael@0 3866 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
michael@0 3867 "cookie-value %s", this, val));
michael@0 3868 rv = Hash(val, hash);
michael@0 3869 // If hash failed, store a string not very likely
michael@0 3870 // to be the result of subsequent hashes
michael@0 3871 if (NS_FAILED(rv))
michael@0 3872 val = "<hash failed>";
michael@0 3873 else
michael@0 3874 val = hash.get();
michael@0 3875
michael@0 3876 LOG((" hashed to %s\n", val));
michael@0 3877 }
michael@0 3878
michael@0 3879 // build cache meta data key and set meta data element...
michael@0 3880 metaKey = prefix + nsDependentCString(token);
michael@0 3881 entry->SetMetaDataElement(metaKey.get(), val);
michael@0 3882 } else {
michael@0 3883 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
michael@0 3884 "clearing metadata for %s", this, token));
michael@0 3885 metaKey = prefix + nsDependentCString(token);
michael@0 3886 entry->SetMetaDataElement(metaKey.get(), nullptr);
michael@0 3887 }
michael@0 3888 }
michael@0 3889 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
michael@0 3890 }
michael@0 3891 }
michael@0 3892 }
michael@0 3893
michael@0 3894
michael@0 3895 // Store the received HTTP head with the cache entry as an element of
michael@0 3896 // the meta data.
michael@0 3897 nsAutoCString head;
michael@0 3898 mResponseHead->Flatten(head, true);
michael@0 3899 rv = entry->SetMetaDataElement("response-head", head.get());
michael@0 3900 if (NS_FAILED(rv)) return rv;
michael@0 3901
michael@0 3902 // Indicate we have successfully finished setting metadata on the cache entry.
michael@0 3903 rv = entry->MetaDataReady();
michael@0 3904
michael@0 3905 return rv;
michael@0 3906 }
michael@0 3907
michael@0 3908 inline void
michael@0 3909 GetAuthType(const char *challenge, nsCString &authType)
michael@0 3910 {
michael@0 3911 const char *p;
michael@0 3912
michael@0 3913 // get the challenge type
michael@0 3914 if ((p = strchr(challenge, ' ')) != nullptr)
michael@0 3915 authType.Assign(challenge, p - challenge);
michael@0 3916 else
michael@0 3917 authType.Assign(challenge);
michael@0 3918 }
michael@0 3919
michael@0 3920 nsresult
michael@0 3921 nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntry *entry)
michael@0 3922 {
michael@0 3923 // Not applicable to proxy authorization...
michael@0 3924 const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
michael@0 3925 if (!val)
michael@0 3926 return NS_OK;
michael@0 3927
michael@0 3928 // eg. [Basic realm="wally world"]
michael@0 3929 nsAutoCString buf;
michael@0 3930 GetAuthType(val, buf);
michael@0 3931 return entry->SetMetaDataElement("auth", buf.get());
michael@0 3932 }
michael@0 3933
michael@0 3934 // Finalize the cache entry
michael@0 3935 // - may need to rewrite response headers if any headers changed
michael@0 3936 // - may need to recalculate the expiration time if any headers changed
michael@0 3937 // - called only for freshly written cache entries
michael@0 3938 nsresult
michael@0 3939 nsHttpChannel::FinalizeCacheEntry()
michael@0 3940 {
michael@0 3941 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
michael@0 3942
michael@0 3943 if (mResponseHead && mResponseHeadersModified) {
michael@0 3944 // Set the expiration time for this cache entry
michael@0 3945 nsresult rv = UpdateExpirationTime();
michael@0 3946 if (NS_FAILED(rv)) return rv;
michael@0 3947 }
michael@0 3948 return NS_OK;
michael@0 3949 }
michael@0 3950
michael@0 3951 // Open an output stream to the cache entry and insert a listener tee into
michael@0 3952 // the chain of response listeners.
michael@0 3953 nsresult
michael@0 3954 nsHttpChannel::InstallCacheListener(int64_t offset)
michael@0 3955 {
michael@0 3956 nsresult rv;
michael@0 3957
michael@0 3958 LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
michael@0 3959
michael@0 3960 MOZ_ASSERT(mCacheEntry);
michael@0 3961 MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial);
michael@0 3962 MOZ_ASSERT(mListener);
michael@0 3963
michael@0 3964 // If the content is compressible and the server has not compressed it,
michael@0 3965 // mark the cache entry for compression.
michael@0 3966 if ((mResponseHead->PeekHeader(nsHttp::Content_Encoding) == nullptr) && (
michael@0 3967 mResponseHead->ContentType().EqualsLiteral(TEXT_HTML) ||
michael@0 3968 mResponseHead->ContentType().EqualsLiteral(TEXT_PLAIN) ||
michael@0 3969 mResponseHead->ContentType().EqualsLiteral(TEXT_CSS) ||
michael@0 3970 mResponseHead->ContentType().EqualsLiteral(TEXT_JAVASCRIPT) ||
michael@0 3971 mResponseHead->ContentType().EqualsLiteral(TEXT_ECMASCRIPT) ||
michael@0 3972 mResponseHead->ContentType().EqualsLiteral(TEXT_XML) ||
michael@0 3973 mResponseHead->ContentType().EqualsLiteral(APPLICATION_JAVASCRIPT) ||
michael@0 3974 mResponseHead->ContentType().EqualsLiteral(APPLICATION_ECMASCRIPT) ||
michael@0 3975 mResponseHead->ContentType().EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
michael@0 3976 mResponseHead->ContentType().EqualsLiteral(APPLICATION_XHTML_XML))) {
michael@0 3977 rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
michael@0 3978 if (NS_FAILED(rv)) {
michael@0 3979 LOG(("unable to mark cache entry for compression"));
michael@0 3980 }
michael@0 3981 }
michael@0 3982
michael@0 3983 LOG(("Trading cache input stream for output stream [channel=%p]", this));
michael@0 3984
michael@0 3985 // We must close the input stream first because cache entries do not
michael@0 3986 // correctly handle having an output stream and input streams open at
michael@0 3987 // the same time.
michael@0 3988 mCacheInputStream.CloseAndRelease();
michael@0 3989
michael@0 3990 nsCOMPtr<nsIOutputStream> out;
michael@0 3991 rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
michael@0 3992 if (rv == NS_ERROR_NOT_AVAILABLE) {
michael@0 3993 LOG((" entry doomed, not writing it [channel=%p]", this));
michael@0 3994 // Entry is already doomed.
michael@0 3995 // This may happen when expiration time is set to past and the entry
michael@0 3996 // has been removed by the background eviction logic.
michael@0 3997 return NS_OK;
michael@0 3998 }
michael@0 3999 if (NS_FAILED(rv)) return rv;
michael@0 4000
michael@0 4001 // XXX disk cache does not support overlapped i/o yet
michael@0 4002 #if 0
michael@0 4003 // Mark entry valid inorder to allow simultaneous reading...
michael@0 4004 rv = mCacheEntry->MarkValid();
michael@0 4005 if (NS_FAILED(rv)) return rv;
michael@0 4006 #endif
michael@0 4007
michael@0 4008 nsCOMPtr<nsIStreamListenerTee> tee =
michael@0 4009 do_CreateInstance(kStreamListenerTeeCID, &rv);
michael@0 4010 if (NS_FAILED(rv)) return rv;
michael@0 4011
michael@0 4012 nsCOMPtr<nsICacheStorageService> serv =
michael@0 4013 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
michael@0 4014 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4015
michael@0 4016 nsCOMPtr<nsIEventTarget> cacheIOTarget;
michael@0 4017 serv->GetIoTarget(getter_AddRefs(cacheIOTarget));
michael@0 4018
michael@0 4019 if (!cacheIOTarget) {
michael@0 4020 LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x "
michael@0 4021 "cacheIOTarget=%p", tee.get(), rv, cacheIOTarget.get()));
michael@0 4022 rv = tee->Init(mListener, out, nullptr);
michael@0 4023 } else {
michael@0 4024 LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
michael@0 4025 rv = tee->InitAsync(mListener, cacheIOTarget, out, nullptr);
michael@0 4026 }
michael@0 4027
michael@0 4028 if (NS_FAILED(rv)) return rv;
michael@0 4029 mListener = tee;
michael@0 4030 return NS_OK;
michael@0 4031 }
michael@0 4032
michael@0 4033 nsresult
michael@0 4034 nsHttpChannel::InstallOfflineCacheListener(int64_t offset)
michael@0 4035 {
michael@0 4036 nsresult rv;
michael@0 4037
michael@0 4038 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
michael@0 4039 mSpec.get()));
michael@0 4040
michael@0 4041 MOZ_ASSERT(mOfflineCacheEntry);
michael@0 4042 MOZ_ASSERT(mListener);
michael@0 4043
michael@0 4044 nsCOMPtr<nsIOutputStream> out;
michael@0 4045 rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
michael@0 4046 if (NS_FAILED(rv)) return rv;
michael@0 4047
michael@0 4048 nsCOMPtr<nsIStreamListenerTee> tee =
michael@0 4049 do_CreateInstance(kStreamListenerTeeCID, &rv);
michael@0 4050 if (NS_FAILED(rv)) return rv;
michael@0 4051
michael@0 4052 rv = tee->Init(mListener, out, nullptr);
michael@0 4053 if (NS_FAILED(rv)) return rv;
michael@0 4054
michael@0 4055 mListener = tee;
michael@0 4056
michael@0 4057 return NS_OK;
michael@0 4058 }
michael@0 4059
michael@0 4060 void
michael@0 4061 nsHttpChannel::ClearBogusContentEncodingIfNeeded()
michael@0 4062 {
michael@0 4063 // For .gz files, apache sends both a Content-Type: application/x-gzip
michael@0 4064 // as well as Content-Encoding: gzip, which is completely wrong. In
michael@0 4065 // this case, we choose to ignore the rogue Content-Encoding header. We
michael@0 4066 // must do this early on so as to prevent it from being seen up stream.
michael@0 4067 // The same problem exists for Content-Encoding: compress in default
michael@0 4068 // Apache installs.
michael@0 4069 if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
michael@0 4070 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
michael@0 4071 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
michael@0 4072 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
michael@0 4073 // clear the Content-Encoding header
michael@0 4074 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
michael@0 4075 }
michael@0 4076 else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
michael@0 4077 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
michael@0 4078 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
michael@0 4079 // clear the Content-Encoding header
michael@0 4080 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
michael@0 4081 }
michael@0 4082 }
michael@0 4083
michael@0 4084 //-----------------------------------------------------------------------------
michael@0 4085 // nsHttpChannel <redirect>
michael@0 4086 //-----------------------------------------------------------------------------
michael@0 4087
michael@0 4088 nsresult
michael@0 4089 nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
michael@0 4090 nsIChannel *newChannel,
michael@0 4091 bool preserveMethod)
michael@0 4092 {
michael@0 4093 LOG(("nsHttpChannel::SetupReplacementChannel "
michael@0 4094 "[this=%p newChannel=%p preserveMethod=%d]",
michael@0 4095 this, newChannel, preserveMethod));
michael@0 4096
michael@0 4097 nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, preserveMethod);
michael@0 4098 if (NS_FAILED(rv))
michael@0 4099 return rv;
michael@0 4100
michael@0 4101 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
michael@0 4102 if (!httpChannel)
michael@0 4103 return NS_OK; // no other options to set
michael@0 4104
michael@0 4105 // convey the mApplyConversion flag (bug 91862)
michael@0 4106 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
michael@0 4107 if (encodedChannel)
michael@0 4108 encodedChannel->SetApplyConversion(mApplyConversion);
michael@0 4109
michael@0 4110 // transfer the resume information
michael@0 4111 if (mResuming) {
michael@0 4112 nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
michael@0 4113 if (!resumableChannel) {
michael@0 4114 NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
michael@0 4115 return NS_ERROR_NOT_RESUMABLE;
michael@0 4116 }
michael@0 4117 resumableChannel->ResumeAt(mStartPos, mEntityID);
michael@0 4118 }
michael@0 4119
michael@0 4120 return NS_OK;
michael@0 4121 }
michael@0 4122
michael@0 4123 nsresult
michael@0 4124 nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType)
michael@0 4125 {
michael@0 4126 LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
michael@0 4127 this, redirectType));
michael@0 4128
michael@0 4129 // The channel is actually starting its operation now, at least because
michael@0 4130 // we want it to appear like being in the waiting phase until now.
michael@0 4131 MOZ_EVENT_TRACER_EXEC(this, "net::http::channel");
michael@0 4132 MOZ_EVENT_TRACER_EXEC(this, "net::http::redirect-callbacks");
michael@0 4133
michael@0 4134 const char *location = mResponseHead->PeekHeader(nsHttp::Location);
michael@0 4135
michael@0 4136 // if a location header was not given, then we can't perform the redirect,
michael@0 4137 // so just carry on as though this were a normal response.
michael@0 4138 if (!location)
michael@0 4139 return NS_ERROR_FAILURE;
michael@0 4140
michael@0 4141 // make sure non-ASCII characters in the location header are escaped.
michael@0 4142 nsAutoCString locationBuf;
michael@0 4143 if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
michael@0 4144 location = locationBuf.get();
michael@0 4145
michael@0 4146 if (mRedirectionLimit == 0) {
michael@0 4147 LOG(("redirection limit reached!\n"));
michael@0 4148 return NS_ERROR_REDIRECT_LOOP;
michael@0 4149 }
michael@0 4150
michael@0 4151 mRedirectType = redirectType;
michael@0 4152
michael@0 4153 LOG(("redirecting to: %s [redirection-limit=%u]\n",
michael@0 4154 location, uint32_t(mRedirectionLimit)));
michael@0 4155
michael@0 4156 nsresult rv = CreateNewURI(location, getter_AddRefs(mRedirectURI));
michael@0 4157
michael@0 4158 if (NS_FAILED(rv)) {
michael@0 4159 LOG(("Invalid URI for redirect: Location: %s\n", location));
michael@0 4160 return NS_ERROR_CORRUPTED_CONTENT;
michael@0 4161 }
michael@0 4162
michael@0 4163 if (mApplicationCache) {
michael@0 4164 // if we are redirected to a different origin check if there is a fallback
michael@0 4165 // cache entry to fall back to. we don't care about file strict
michael@0 4166 // checking, at least mURI is not a file URI.
michael@0 4167 if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
michael@0 4168 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
michael@0 4169 bool waitingForRedirectCallback;
michael@0 4170 (void)ProcessFallback(&waitingForRedirectCallback);
michael@0 4171 if (waitingForRedirectCallback)
michael@0 4172 return NS_OK;
michael@0 4173 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
michael@0 4174 }
michael@0 4175 }
michael@0 4176
michael@0 4177 return ContinueProcessRedirectionAfterFallback(NS_OK);
michael@0 4178 }
michael@0 4179
michael@0 4180 // Creates an URI to the given location using current URI for base and charset
michael@0 4181 nsresult
michael@0 4182 nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
michael@0 4183 {
michael@0 4184 nsCOMPtr<nsIIOService> ioService;
michael@0 4185 nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
michael@0 4186 if (NS_FAILED(rv)) return rv;
michael@0 4187
michael@0 4188 // the new uri should inherit the origin charset of the current uri
michael@0 4189 nsAutoCString originCharset;
michael@0 4190 rv = mURI->GetOriginCharset(originCharset);
michael@0 4191 if (NS_FAILED(rv))
michael@0 4192 originCharset.Truncate();
michael@0 4193
michael@0 4194 return ioService->NewURI(nsDependentCString(loc),
michael@0 4195 originCharset.get(),
michael@0 4196 mURI,
michael@0 4197 newURI);
michael@0 4198 }
michael@0 4199
michael@0 4200 nsresult
michael@0 4201 nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
michael@0 4202 {
michael@0 4203 if (NS_SUCCEEDED(rv) && mFallingBack) {
michael@0 4204 // do not continue with redirect processing, fallback is in
michael@0 4205 // progress now.
michael@0 4206 return NS_OK;
michael@0 4207 }
michael@0 4208
michael@0 4209 // Kill the current cache entry if we are redirecting
michael@0 4210 // back to ourself.
michael@0 4211 bool redirectingBackToSameURI = false;
michael@0 4212 if (mCacheEntry && mCacheEntryIsWriteOnly &&
michael@0 4213 NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
michael@0 4214 redirectingBackToSameURI)
michael@0 4215 mCacheEntry->AsyncDoom(nullptr);
michael@0 4216
michael@0 4217 // move the reference of the old location to the new one if the new
michael@0 4218 // one has none.
michael@0 4219 nsAutoCString ref;
michael@0 4220 rv = mRedirectURI->GetRef(ref);
michael@0 4221 if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
michael@0 4222 mURI->GetRef(ref);
michael@0 4223 if (!ref.IsEmpty()) {
michael@0 4224 // NOTE: SetRef will fail if mRedirectURI is immutable
michael@0 4225 // (e.g. an about: URI)... Oh well.
michael@0 4226 mRedirectURI->SetRef(ref);
michael@0 4227 }
michael@0 4228 }
michael@0 4229
michael@0 4230 bool rewriteToGET = ShouldRewriteRedirectToGET(mRedirectType,
michael@0 4231 mRequestHead.ParsedMethod());
michael@0 4232
michael@0 4233 // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
michael@0 4234 if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
michael@0 4235 rv = PromptTempRedirect();
michael@0 4236 if (NS_FAILED(rv)) return rv;
michael@0 4237 }
michael@0 4238
michael@0 4239 nsCOMPtr<nsIIOService> ioService;
michael@0 4240 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
michael@0 4241 if (NS_FAILED(rv)) return rv;
michael@0 4242
michael@0 4243 nsCOMPtr<nsIChannel> newChannel;
michael@0 4244 rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel));
michael@0 4245 if (NS_FAILED(rv)) return rv;
michael@0 4246
michael@0 4247 rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET);
michael@0 4248 if (NS_FAILED(rv)) return rv;
michael@0 4249
michael@0 4250 uint32_t redirectFlags;
michael@0 4251 if (nsHttp::IsPermanentRedirect(mRedirectType))
michael@0 4252 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
michael@0 4253 else
michael@0 4254 redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
michael@0 4255
michael@0 4256 // verify that this is a legal redirect
michael@0 4257 mRedirectChannel = newChannel;
michael@0 4258
michael@0 4259 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
michael@0 4260 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
michael@0 4261
michael@0 4262 if (NS_SUCCEEDED(rv))
michael@0 4263 rv = WaitForRedirectCallback();
michael@0 4264
michael@0 4265 if (NS_FAILED(rv)) {
michael@0 4266 AutoRedirectVetoNotifier notifier(this);
michael@0 4267 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
michael@0 4268 }
michael@0 4269
michael@0 4270 return rv;
michael@0 4271 }
michael@0 4272
michael@0 4273 nsresult
michael@0 4274 nsHttpChannel::ContinueProcessRedirection(nsresult rv)
michael@0 4275 {
michael@0 4276 AutoRedirectVetoNotifier notifier(this);
michael@0 4277
michael@0 4278 LOG(("ContinueProcessRedirection [rv=%x]\n", rv));
michael@0 4279 if (NS_FAILED(rv))
michael@0 4280 return rv;
michael@0 4281
michael@0 4282 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
michael@0 4283
michael@0 4284 // Make sure to do this _after_ calling OnChannelRedirect
michael@0 4285 mRedirectChannel->SetOriginalURI(mOriginalURI);
michael@0 4286
michael@0 4287 // And now, the deprecated way
michael@0 4288 nsCOMPtr<nsIHttpEventSink> httpEventSink;
michael@0 4289 GetCallback(httpEventSink);
michael@0 4290 if (httpEventSink) {
michael@0 4291 // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
michael@0 4292 // versions.
michael@0 4293 rv = httpEventSink->OnRedirect(this, mRedirectChannel);
michael@0 4294 if (NS_FAILED(rv))
michael@0 4295 return rv;
michael@0 4296 }
michael@0 4297 // XXX we used to talk directly with the script security manager, but that
michael@0 4298 // should really be handled by the event sink implementation.
michael@0 4299
michael@0 4300 // begin loading the new channel
michael@0 4301 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
michael@0 4302
michael@0 4303 if (NS_FAILED(rv))
michael@0 4304 return rv;
michael@0 4305
michael@0 4306 // close down this channel
michael@0 4307 Cancel(NS_BINDING_REDIRECTED);
michael@0 4308
michael@0 4309 notifier.RedirectSucceeded();
michael@0 4310
michael@0 4311 ReleaseListeners();
michael@0 4312
michael@0 4313 return NS_OK;
michael@0 4314 }
michael@0 4315
michael@0 4316 //-----------------------------------------------------------------------------
michael@0 4317 // nsHttpChannel <auth>
michael@0 4318 //-----------------------------------------------------------------------------
michael@0 4319
michael@0 4320 NS_IMETHODIMP nsHttpChannel::OnAuthAvailable()
michael@0 4321 {
michael@0 4322 LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
michael@0 4323
michael@0 4324 // setting mAuthRetryPending flag and resuming the transaction
michael@0 4325 // triggers process of throwing away the unauthenticated data already
michael@0 4326 // coming from the network
michael@0 4327 mAuthRetryPending = true;
michael@0 4328 mProxyAuthPending = false;
michael@0 4329 LOG(("Resuming the transaction, we got credentials from user"));
michael@0 4330 mTransactionPump->Resume();
michael@0 4331
michael@0 4332 return NS_OK;
michael@0 4333 }
michael@0 4334
michael@0 4335 NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel)
michael@0 4336 {
michael@0 4337 LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
michael@0 4338
michael@0 4339 if (mTransactionPump) {
michael@0 4340 // If the channel is trying to authenticate to a proxy and
michael@0 4341 // that was canceled we cannot show the http response body
michael@0 4342 // from the 40x as that might mislead the user into thinking
michael@0 4343 // it was a end host response instead of a proxy reponse.
michael@0 4344 // This must check explicitly whether a proxy auth was being done
michael@0 4345 // because we do want to show the content if this is an error from
michael@0 4346 // the origin server.
michael@0 4347 if (mProxyAuthPending)
michael@0 4348 Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
michael@0 4349
michael@0 4350 // ensure call of OnStartRequest of the current listener here,
michael@0 4351 // it would not be called otherwise at all
michael@0 4352 nsresult rv = CallOnStartRequest();
michael@0 4353
michael@0 4354 // drop mAuthRetryPending flag and resume the transaction
michael@0 4355 // this resumes load of the unauthenticated content data (which
michael@0 4356 // may have been canceled if we don't want to show it)
michael@0 4357 mAuthRetryPending = false;
michael@0 4358 LOG(("Resuming the transaction, user cancelled the auth dialog"));
michael@0 4359 mTransactionPump->Resume();
michael@0 4360
michael@0 4361 if (NS_FAILED(rv))
michael@0 4362 mTransactionPump->Cancel(rv);
michael@0 4363 }
michael@0 4364
michael@0 4365 mProxyAuthPending = false;
michael@0 4366 return NS_OK;
michael@0 4367 }
michael@0 4368
michael@0 4369 //-----------------------------------------------------------------------------
michael@0 4370 // nsHttpChannel::nsISupports
michael@0 4371 //-----------------------------------------------------------------------------
michael@0 4372
michael@0 4373 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
michael@0 4374 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
michael@0 4375
michael@0 4376 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
michael@0 4377 NS_INTERFACE_MAP_ENTRY(nsIRequest)
michael@0 4378 NS_INTERFACE_MAP_ENTRY(nsIChannel)
michael@0 4379 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
michael@0 4380 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
michael@0 4381 NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
michael@0 4382 NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
michael@0 4383 NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
michael@0 4384 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
michael@0 4385 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
michael@0 4386 NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
michael@0 4387 NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
michael@0 4388 NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
michael@0 4389 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
michael@0 4390 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
michael@0 4391 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
michael@0 4392 NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
michael@0 4393 NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
michael@0 4394 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
michael@0 4395 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
michael@0 4396 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
michael@0 4397 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
michael@0 4398 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
michael@0 4399 NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
michael@0 4400 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
michael@0 4401 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
michael@0 4402
michael@0 4403 //-----------------------------------------------------------------------------
michael@0 4404 // nsHttpChannel::nsIRequest
michael@0 4405 //-----------------------------------------------------------------------------
michael@0 4406
michael@0 4407 NS_IMETHODIMP
michael@0 4408 nsHttpChannel::Cancel(nsresult status)
michael@0 4409 {
michael@0 4410 MOZ_ASSERT(NS_IsMainThread());
michael@0 4411
michael@0 4412 LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status));
michael@0 4413 if (mCanceled) {
michael@0 4414 LOG((" ignoring; already canceled\n"));
michael@0 4415 return NS_OK;
michael@0 4416 }
michael@0 4417 if (mWaitingForRedirectCallback) {
michael@0 4418 LOG(("channel canceled during wait for redirect callback"));
michael@0 4419 }
michael@0 4420 mCanceled = true;
michael@0 4421 mStatus = status;
michael@0 4422 if (mProxyRequest)
michael@0 4423 mProxyRequest->Cancel(status);
michael@0 4424 if (mTransaction)
michael@0 4425 gHttpHandler->CancelTransaction(mTransaction, status);
michael@0 4426 if (mTransactionPump)
michael@0 4427 mTransactionPump->Cancel(status);
michael@0 4428 mCacheInputStream.CloseAndRelease();
michael@0 4429 if (mCachePump)
michael@0 4430 mCachePump->Cancel(status);
michael@0 4431 if (mAuthProvider)
michael@0 4432 mAuthProvider->Cancel(status);
michael@0 4433 return NS_OK;
michael@0 4434 }
michael@0 4435
michael@0 4436 NS_IMETHODIMP
michael@0 4437 nsHttpChannel::Suspend()
michael@0 4438 {
michael@0 4439 NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
michael@0 4440
michael@0 4441 LOG(("nsHttpChannel::Suspend [this=%p]\n", this));
michael@0 4442
michael@0 4443 ++mSuspendCount;
michael@0 4444
michael@0 4445 if (mTransactionPump)
michael@0 4446 return mTransactionPump->Suspend();
michael@0 4447 if (mCachePump)
michael@0 4448 return mCachePump->Suspend();
michael@0 4449
michael@0 4450 return NS_OK;
michael@0 4451 }
michael@0 4452
michael@0 4453 NS_IMETHODIMP
michael@0 4454 nsHttpChannel::Resume()
michael@0 4455 {
michael@0 4456 NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
michael@0 4457
michael@0 4458 LOG(("nsHttpChannel::Resume [this=%p]\n", this));
michael@0 4459
michael@0 4460 if (--mSuspendCount == 0 && mCallOnResume) {
michael@0 4461 nsresult rv = AsyncCall(mCallOnResume);
michael@0 4462 mCallOnResume = nullptr;
michael@0 4463 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4464 }
michael@0 4465
michael@0 4466 if (mTransactionPump)
michael@0 4467 return mTransactionPump->Resume();
michael@0 4468 if (mCachePump)
michael@0 4469 return mCachePump->Resume();
michael@0 4470
michael@0 4471 return NS_OK;
michael@0 4472 }
michael@0 4473
michael@0 4474 //-----------------------------------------------------------------------------
michael@0 4475 // nsHttpChannel::nsIChannel
michael@0 4476 //-----------------------------------------------------------------------------
michael@0 4477
michael@0 4478 NS_IMETHODIMP
michael@0 4479 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
michael@0 4480 {
michael@0 4481 NS_ENSURE_ARG_POINTER(securityInfo);
michael@0 4482 *securityInfo = mSecurityInfo;
michael@0 4483 NS_IF_ADDREF(*securityInfo);
michael@0 4484 return NS_OK;
michael@0 4485 }
michael@0 4486
michael@0 4487 NS_IMETHODIMP
michael@0 4488 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
michael@0 4489 {
michael@0 4490 MOZ_EVENT_TRACER_WAIT(this, "net::http::channel");
michael@0 4491
michael@0 4492 LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
michael@0 4493
michael@0 4494 NS_ENSURE_ARG_POINTER(listener);
michael@0 4495 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
michael@0 4496 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
michael@0 4497
michael@0 4498 nsresult rv;
michael@0 4499
michael@0 4500 rv = NS_CheckPortSafety(mURI);
michael@0 4501 if (NS_FAILED(rv)) {
michael@0 4502 ReleaseListeners();
michael@0 4503 return rv;
michael@0 4504 }
michael@0 4505
michael@0 4506 // Remember the cookie header that was set, if any
michael@0 4507 const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
michael@0 4508 if (cookieHeader) {
michael@0 4509 mUserSetCookieHeader = cookieHeader;
michael@0 4510 }
michael@0 4511
michael@0 4512 AddCookiesToRequest();
michael@0 4513
michael@0 4514 // notify "http-on-opening-request" observers, but not if this is a redirect
michael@0 4515 if (!(mLoadFlags & LOAD_REPLACE)) {
michael@0 4516 gHttpHandler->OnOpeningRequest(this);
michael@0 4517 }
michael@0 4518
michael@0 4519 mIsPending = true;
michael@0 4520 mWasOpened = true;
michael@0 4521
michael@0 4522 mListener = listener;
michael@0 4523 mListenerContext = context;
michael@0 4524
michael@0 4525 // add ourselves to the load group. from this point forward, we'll report
michael@0 4526 // all failures asynchronously.
michael@0 4527 if (mLoadGroup)
michael@0 4528 mLoadGroup->AddRequest(this, nullptr);
michael@0 4529
michael@0 4530 // record asyncopen time unconditionally and clear it if we
michael@0 4531 // don't want it after OnModifyRequest() weighs in. But waiting for
michael@0 4532 // that to complete would mean we don't include proxy resolution in the
michael@0 4533 // timing.
michael@0 4534 mAsyncOpenTime = TimeStamp::Now();
michael@0 4535
michael@0 4536 // the only time we would already know the proxy information at this
michael@0 4537 // point would be if we were proxying a non-http protocol like ftp
michael@0 4538 if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy()))
michael@0 4539 return NS_OK;
michael@0 4540
michael@0 4541 rv = BeginConnect();
michael@0 4542 if (NS_FAILED(rv))
michael@0 4543 ReleaseListeners();
michael@0 4544
michael@0 4545 return rv;
michael@0 4546 }
michael@0 4547
michael@0 4548 nsresult
michael@0 4549 nsHttpChannel::BeginConnect()
michael@0 4550 {
michael@0 4551 LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
michael@0 4552 nsresult rv;
michael@0 4553
michael@0 4554 // Construct connection info object
michael@0 4555 nsAutoCString host;
michael@0 4556 int32_t port = -1;
michael@0 4557 nsAutoCString username;
michael@0 4558 bool usingSSL = false;
michael@0 4559
michael@0 4560 rv = mURI->SchemeIs("https", &usingSSL);
michael@0 4561 if (NS_SUCCEEDED(rv))
michael@0 4562 rv = mURI->GetAsciiHost(host);
michael@0 4563 if (NS_SUCCEEDED(rv))
michael@0 4564 rv = mURI->GetPort(&port);
michael@0 4565 if (NS_SUCCEEDED(rv))
michael@0 4566 mURI->GetUsername(username);
michael@0 4567 if (NS_SUCCEEDED(rv))
michael@0 4568 rv = mURI->GetAsciiSpec(mSpec);
michael@0 4569 if (NS_FAILED(rv))
michael@0 4570 return rv;
michael@0 4571
michael@0 4572 // Reject the URL if it doesn't specify a host
michael@0 4573 if (host.IsEmpty())
michael@0 4574 return NS_ERROR_MALFORMED_URI;
michael@0 4575 LOG(("host=%s port=%d\n", host.get(), port));
michael@0 4576 LOG(("uri=%s\n", mSpec.get()));
michael@0 4577
michael@0 4578 nsCOMPtr<nsProxyInfo> proxyInfo;
michael@0 4579 if (mProxyInfo)
michael@0 4580 proxyInfo = do_QueryInterface(mProxyInfo);
michael@0 4581
michael@0 4582 mConnectionInfo = new nsHttpConnectionInfo(host, port, username, proxyInfo, usingSSL);
michael@0 4583
michael@0 4584 mAuthProvider =
michael@0 4585 do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
michael@0 4586 &rv);
michael@0 4587 if (NS_SUCCEEDED(rv))
michael@0 4588 rv = mAuthProvider->Init(this);
michael@0 4589 if (NS_FAILED(rv))
michael@0 4590 return rv;
michael@0 4591
michael@0 4592 // check to see if authorization headers should be included
michael@0 4593 mAuthProvider->AddAuthorizationHeaders();
michael@0 4594
michael@0 4595 // notify "http-on-modify-request" observers
michael@0 4596 CallOnModifyRequestObservers();
michael@0 4597
michael@0 4598 // Check to see if we should redirect this channel elsewhere by
michael@0 4599 // nsIHttpChannel.redirectTo API request
michael@0 4600 if (mAPIRedirectToURI) {
michael@0 4601 return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
michael@0 4602 }
michael@0 4603
michael@0 4604 // If mTimingEnabled flag is not set after OnModifyRequest() then
michael@0 4605 // clear the already recorded AsyncOpen value for consistency.
michael@0 4606 if (!mTimingEnabled)
michael@0 4607 mAsyncOpenTime = TimeStamp();
michael@0 4608
michael@0 4609 // when proxying only use the pipeline bit if ProxyPipelining() allows it.
michael@0 4610 if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
michael@0 4611 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
michael@0 4612 if (gHttpHandler->ProxyPipelining())
michael@0 4613 mCaps |= NS_HTTP_ALLOW_PIPELINING;
michael@0 4614 }
michael@0 4615
michael@0 4616 // if this somehow fails we can go on without it
michael@0 4617 gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps);
michael@0 4618
michael@0 4619 if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
michael@0 4620 mCaps |= NS_HTTP_REFRESH_DNS;
michael@0 4621
michael@0 4622 if (!mConnectionInfo->UsingHttpProxy()) {
michael@0 4623 // Start a DNS lookup very early in case the real open is queued the DNS can
michael@0 4624 // happen in parallel. Do not do so in the presence of an HTTP proxy as
michael@0 4625 // all lookups other than for the proxy itself are done by the proxy.
michael@0 4626 //
michael@0 4627 // We keep the DNS prefetch object around so that we can retrieve
michael@0 4628 // timing information from it. There is no guarantee that we actually
michael@0 4629 // use the DNS prefetch data for the real connection, but as we keep
michael@0 4630 // this data around for 3 minutes by default, this should almost always
michael@0 4631 // be correct, and even when it isn't, the timing still represents _a_
michael@0 4632 // valid DNS lookup timing for the site, even if it is not _the_
michael@0 4633 // timing we used.
michael@0 4634 LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n",
michael@0 4635 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
michael@0 4636 mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled);
michael@0 4637 mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
michael@0 4638 }
michael@0 4639
michael@0 4640 // Adjust mCaps according to our request headers:
michael@0 4641 // - If "Connection: close" is set as a request header, then do not bother
michael@0 4642 // trying to establish a keep-alive connection.
michael@0 4643 if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
michael@0 4644 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
michael@0 4645
michael@0 4646 if (gHttpHandler->CriticalRequestPrioritization()) {
michael@0 4647 if (mLoadAsBlocking)
michael@0 4648 mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
michael@0 4649 if (mLoadUnblocked)
michael@0 4650 mCaps |= NS_HTTP_LOAD_UNBLOCKED;
michael@0 4651 }
michael@0 4652
michael@0 4653 // Force-Reload should reset the persistent connection pool for this host
michael@0 4654 if (mLoadFlags & LOAD_FRESH_CONNECTION) {
michael@0 4655 // just the initial document resets the whole pool
michael@0 4656 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
michael@0 4657 gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
michael@0 4658 }
michael@0 4659 // each sub resource gets a fresh connection
michael@0 4660 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
michael@0 4661 }
michael@0 4662
michael@0 4663 // We may have been cancelled already, either by on-modify-request
michael@0 4664 // listeners or by load group observers; in that case, we should
michael@0 4665 // not send the request to the server
michael@0 4666 if (mCanceled)
michael@0 4667 rv = mStatus;
michael@0 4668 else
michael@0 4669 rv = Connect();
michael@0 4670 if (NS_FAILED(rv)) {
michael@0 4671 LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
michael@0 4672 CloseCacheEntry(true);
michael@0 4673 AsyncAbort(rv);
michael@0 4674 } else if (mLoadFlags & LOAD_CLASSIFY_URI) {
michael@0 4675 nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
michael@0 4676 rv = classifier->Start(this);
michael@0 4677 if (NS_FAILED(rv)) {
michael@0 4678 Cancel(rv);
michael@0 4679 return rv;
michael@0 4680 }
michael@0 4681 }
michael@0 4682
michael@0 4683 return NS_OK;
michael@0 4684 }
michael@0 4685
michael@0 4686 //-----------------------------------------------------------------------------
michael@0 4687 // nsHttpChannel::nsIHttpChannelInternal
michael@0 4688 //-----------------------------------------------------------------------------
michael@0 4689
michael@0 4690 NS_IMETHODIMP
michael@0 4691 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
michael@0 4692 {
michael@0 4693 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 4694
michael@0 4695 LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]",
michael@0 4696 this, aFallbackKey));
michael@0 4697 mFallbackChannel = true;
michael@0 4698 mFallbackKey = aFallbackKey;
michael@0 4699
michael@0 4700 return NS_OK;
michael@0 4701 }
michael@0 4702
michael@0 4703 //-----------------------------------------------------------------------------
michael@0 4704 // nsHttpChannel::nsISupportsPriority
michael@0 4705 //-----------------------------------------------------------------------------
michael@0 4706
michael@0 4707 NS_IMETHODIMP
michael@0 4708 nsHttpChannel::SetPriority(int32_t value)
michael@0 4709 {
michael@0 4710 int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
michael@0 4711 if (mPriority == newValue)
michael@0 4712 return NS_OK;
michael@0 4713 mPriority = newValue;
michael@0 4714 if (mTransaction)
michael@0 4715 gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
michael@0 4716 return NS_OK;
michael@0 4717 }
michael@0 4718
michael@0 4719 //-----------------------------------------------------------------------------
michael@0 4720 // nsHttpChannel::nsIProtocolProxyCallback
michael@0 4721 //-----------------------------------------------------------------------------
michael@0 4722
michael@0 4723 NS_IMETHODIMP
michael@0 4724 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
michael@0 4725 nsIProxyInfo *pi, nsresult status)
michael@0 4726 {
michael@0 4727 LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%x mStatus=%x]\n",
michael@0 4728 this, pi, status, mStatus));
michael@0 4729 mProxyRequest = nullptr;
michael@0 4730
michael@0 4731 nsresult rv;
michael@0 4732
michael@0 4733 // If status is a failure code, then it means that we failed to resolve
michael@0 4734 // proxy info. That is a non-fatal error assuming it wasn't because the
michael@0 4735 // request was canceled. We just failover to DIRECT when proxy resolution
michael@0 4736 // fails (failure can mean that the PAC URL could not be loaded).
michael@0 4737
michael@0 4738 if (NS_SUCCEEDED(status))
michael@0 4739 mProxyInfo = pi;
michael@0 4740
michael@0 4741 if (!gHttpHandler->Active()) {
michael@0 4742 LOG(("nsHttpChannel::OnProxyAvailable [this=%p] "
michael@0 4743 "Handler no longer active.\n", this));
michael@0 4744 rv = NS_ERROR_NOT_AVAILABLE;
michael@0 4745 }
michael@0 4746 else {
michael@0 4747 rv = BeginConnect();
michael@0 4748 }
michael@0 4749
michael@0 4750 if (NS_FAILED(rv)) {
michael@0 4751 Cancel(rv);
michael@0 4752 // Calling OnStart/OnStop synchronously here would mean doing it before
michael@0 4753 // returning from AsyncOpen which is a contract violation. Do it async.
michael@0 4754 nsRefPtr<nsRunnableMethod<HttpBaseChannel> > event =
michael@0 4755 NS_NewRunnableMethod(this, &nsHttpChannel::DoNotifyListener);
michael@0 4756 rv = NS_DispatchToCurrentThread(event);
michael@0 4757 if (NS_FAILED(rv)) {
michael@0 4758 NS_WARNING("Failed To Dispatch DoNotifyListener");
michael@0 4759 }
michael@0 4760 }
michael@0 4761 return rv;
michael@0 4762 }
michael@0 4763
michael@0 4764 //-----------------------------------------------------------------------------
michael@0 4765 // nsHttpChannel::nsIProxiedChannel
michael@0 4766 //-----------------------------------------------------------------------------
michael@0 4767
michael@0 4768 NS_IMETHODIMP
michael@0 4769 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
michael@0 4770 {
michael@0 4771 if (!mConnectionInfo)
michael@0 4772 *result = mProxyInfo;
michael@0 4773 else
michael@0 4774 *result = mConnectionInfo->ProxyInfo();
michael@0 4775 NS_IF_ADDREF(*result);
michael@0 4776 return NS_OK;
michael@0 4777 }
michael@0 4778
michael@0 4779 //-----------------------------------------------------------------------------
michael@0 4780 // nsHttpChannel::nsITimedChannel
michael@0 4781 //-----------------------------------------------------------------------------
michael@0 4782
michael@0 4783 NS_IMETHODIMP
michael@0 4784 nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) {
michael@0 4785 if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
michael@0 4786 *_retval = mDNSPrefetch->StartTimestamp();
michael@0 4787 else if (mTransaction)
michael@0 4788 *_retval = mTransaction->Timings().domainLookupStart;
michael@0 4789 else
michael@0 4790 *_retval = mTransactionTimings.domainLookupStart;
michael@0 4791 return NS_OK;
michael@0 4792 }
michael@0 4793
michael@0 4794 NS_IMETHODIMP
michael@0 4795 nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) {
michael@0 4796 if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
michael@0 4797 *_retval = mDNSPrefetch->EndTimestamp();
michael@0 4798 else if (mTransaction)
michael@0 4799 *_retval = mTransaction->Timings().domainLookupEnd;
michael@0 4800 else
michael@0 4801 *_retval = mTransactionTimings.domainLookupEnd;
michael@0 4802 return NS_OK;
michael@0 4803 }
michael@0 4804
michael@0 4805 NS_IMETHODIMP
michael@0 4806 nsHttpChannel::GetConnectStart(TimeStamp* _retval) {
michael@0 4807 if (mTransaction)
michael@0 4808 *_retval = mTransaction->Timings().connectStart;
michael@0 4809 else
michael@0 4810 *_retval = mTransactionTimings.connectStart;
michael@0 4811 return NS_OK;
michael@0 4812 }
michael@0 4813
michael@0 4814 NS_IMETHODIMP
michael@0 4815 nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
michael@0 4816 if (mTransaction)
michael@0 4817 *_retval = mTransaction->Timings().connectEnd;
michael@0 4818 else
michael@0 4819 *_retval = mTransactionTimings.connectEnd;
michael@0 4820 return NS_OK;
michael@0 4821 }
michael@0 4822
michael@0 4823 NS_IMETHODIMP
michael@0 4824 nsHttpChannel::GetRequestStart(TimeStamp* _retval) {
michael@0 4825 if (mTransaction)
michael@0 4826 *_retval = mTransaction->Timings().requestStart;
michael@0 4827 else
michael@0 4828 *_retval = mTransactionTimings.requestStart;
michael@0 4829 return NS_OK;
michael@0 4830 }
michael@0 4831
michael@0 4832 NS_IMETHODIMP
michael@0 4833 nsHttpChannel::GetResponseStart(TimeStamp* _retval) {
michael@0 4834 if (mTransaction)
michael@0 4835 *_retval = mTransaction->Timings().responseStart;
michael@0 4836 else
michael@0 4837 *_retval = mTransactionTimings.responseStart;
michael@0 4838 return NS_OK;
michael@0 4839 }
michael@0 4840
michael@0 4841 NS_IMETHODIMP
michael@0 4842 nsHttpChannel::GetResponseEnd(TimeStamp* _retval) {
michael@0 4843 if (mTransaction)
michael@0 4844 *_retval = mTransaction->Timings().responseEnd;
michael@0 4845 else
michael@0 4846 *_retval = mTransactionTimings.responseEnd;
michael@0 4847 return NS_OK;
michael@0 4848 }
michael@0 4849
michael@0 4850 //-----------------------------------------------------------------------------
michael@0 4851 // nsHttpChannel::nsIHttpAuthenticableChannel
michael@0 4852 //-----------------------------------------------------------------------------
michael@0 4853
michael@0 4854 NS_IMETHODIMP
michael@0 4855 nsHttpChannel::GetIsSSL(bool *aIsSSL)
michael@0 4856 {
michael@0 4857 *aIsSSL = mConnectionInfo->UsingSSL();
michael@0 4858 return NS_OK;
michael@0 4859 }
michael@0 4860
michael@0 4861 NS_IMETHODIMP
michael@0 4862 nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect)
michael@0 4863 {
michael@0 4864 *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
michael@0 4865 return NS_OK;
michael@0 4866 }
michael@0 4867
michael@0 4868 NS_IMETHODIMP
michael@0 4869 nsHttpChannel::GetServerResponseHeader(nsACString &value)
michael@0 4870 {
michael@0 4871 if (!mResponseHead)
michael@0 4872 return NS_ERROR_NOT_AVAILABLE;
michael@0 4873 return mResponseHead->GetHeader(nsHttp::Server, value);
michael@0 4874 }
michael@0 4875
michael@0 4876 NS_IMETHODIMP
michael@0 4877 nsHttpChannel::GetProxyChallenges(nsACString &value)
michael@0 4878 {
michael@0 4879 if (!mResponseHead)
michael@0 4880 return NS_ERROR_UNEXPECTED;
michael@0 4881 return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
michael@0 4882 }
michael@0 4883
michael@0 4884 NS_IMETHODIMP
michael@0 4885 nsHttpChannel::GetWWWChallenges(nsACString &value)
michael@0 4886 {
michael@0 4887 if (!mResponseHead)
michael@0 4888 return NS_ERROR_UNEXPECTED;
michael@0 4889 return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
michael@0 4890 }
michael@0 4891
michael@0 4892 NS_IMETHODIMP
michael@0 4893 nsHttpChannel::SetProxyCredentials(const nsACString &value)
michael@0 4894 {
michael@0 4895 return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
michael@0 4896 }
michael@0 4897
michael@0 4898 NS_IMETHODIMP
michael@0 4899 nsHttpChannel::SetWWWCredentials(const nsACString &value)
michael@0 4900 {
michael@0 4901 return mRequestHead.SetHeader(nsHttp::Authorization, value);
michael@0 4902 }
michael@0 4903
michael@0 4904 //-----------------------------------------------------------------------------
michael@0 4905 // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
michael@0 4906 // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
michael@0 4907 //
michael@0 4908
michael@0 4909 NS_IMETHODIMP
michael@0 4910 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
michael@0 4911 {
michael@0 4912 return HttpBaseChannel::GetLoadFlags(aLoadFlags);
michael@0 4913 }
michael@0 4914
michael@0 4915 NS_IMETHODIMP
michael@0 4916 nsHttpChannel::GetURI(nsIURI **aURI)
michael@0 4917 {
michael@0 4918 return HttpBaseChannel::GetURI(aURI);
michael@0 4919 }
michael@0 4920
michael@0 4921 NS_IMETHODIMP
michael@0 4922 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
michael@0 4923 {
michael@0 4924 return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
michael@0 4925 }
michael@0 4926
michael@0 4927 NS_IMETHODIMP
michael@0 4928 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
michael@0 4929 {
michael@0 4930 return HttpBaseChannel::GetLoadGroup(aLoadGroup);
michael@0 4931 }
michael@0 4932
michael@0 4933 NS_IMETHODIMP
michael@0 4934 nsHttpChannel::GetRequestMethod(nsACString& aMethod)
michael@0 4935 {
michael@0 4936 return HttpBaseChannel::GetRequestMethod(aMethod);
michael@0 4937 }
michael@0 4938
michael@0 4939
michael@0 4940 //-----------------------------------------------------------------------------
michael@0 4941 // nsHttpChannel::nsIRequestObserver
michael@0 4942 //-----------------------------------------------------------------------------
michael@0 4943
michael@0 4944 NS_IMETHODIMP
michael@0 4945 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
michael@0 4946 {
michael@0 4947 PROFILER_LABEL("nsHttpChannel", "OnStartRequest");
michael@0 4948 if (!(mCanceled || NS_FAILED(mStatus))) {
michael@0 4949 // capture the request's status, so our consumers will know ASAP of any
michael@0 4950 // connection failures, etc - bug 93581
michael@0 4951 request->GetStatus(&mStatus);
michael@0 4952 }
michael@0 4953
michael@0 4954 LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n",
michael@0 4955 this, request, mStatus));
michael@0 4956
michael@0 4957 // Make sure things are what we expect them to be...
michael@0 4958 MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
michael@0 4959 "Unexpected request");
michael@0 4960 MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
michael@0 4961 "If we have both pumps, the cache content must be partial");
michael@0 4962
michael@0 4963 if (!mSecurityInfo && !mCachePump && mTransaction) {
michael@0 4964 // grab the security info from the connection object; the transaction
michael@0 4965 // is guaranteed to own a reference to the connection.
michael@0 4966 mSecurityInfo = mTransaction->SecurityInfo();
michael@0 4967 }
michael@0 4968
michael@0 4969 // don't enter this block if we're reading from the cache...
michael@0 4970 if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
michael@0 4971 // mTransactionPump doesn't hit OnInputStreamReady and call this until
michael@0 4972 // all of the response headers have been acquired, so we can take ownership
michael@0 4973 // of them from the transaction.
michael@0 4974 mResponseHead = mTransaction->TakeResponseHead();
michael@0 4975 // the response head may be null if the transaction was cancelled. in
michael@0 4976 // which case we just need to call OnStartRequest/OnStopRequest.
michael@0 4977 if (mResponseHead)
michael@0 4978 return ProcessResponse();
michael@0 4979
michael@0 4980 NS_WARNING("No response head in OnStartRequest");
michael@0 4981 }
michael@0 4982
michael@0 4983 // cache file could be deleted on our behalf, reload from network here.
michael@0 4984 if (mCacheEntry && mCachePump && CACHE_FILE_GONE(mStatus)) {
michael@0 4985 LOG((" cache file gone, reloading from server"));
michael@0 4986 mCacheEntry->AsyncDoom(nullptr);
michael@0 4987 nsresult rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
michael@0 4988 if (NS_SUCCEEDED(rv))
michael@0 4989 return NS_OK;
michael@0 4990 }
michael@0 4991
michael@0 4992 // avoid crashing if mListener happens to be null...
michael@0 4993 if (!mListener) {
michael@0 4994 NS_NOTREACHED("mListener is null");
michael@0 4995 return NS_OK;
michael@0 4996 }
michael@0 4997
michael@0 4998 // on proxy errors, try to failover
michael@0 4999 if (mConnectionInfo->ProxyInfo() &&
michael@0 5000 (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
michael@0 5001 mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
michael@0 5002 mStatus == NS_ERROR_NET_TIMEOUT)) {
michael@0 5003
michael@0 5004 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
michael@0 5005 if (NS_SUCCEEDED(ProxyFailover()))
michael@0 5006 return NS_OK;
michael@0 5007 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
michael@0 5008 }
michael@0 5009
michael@0 5010 return ContinueOnStartRequest2(NS_OK);
michael@0 5011 }
michael@0 5012
michael@0 5013 nsresult
michael@0 5014 nsHttpChannel::ContinueOnStartRequest1(nsresult result)
michael@0 5015 {
michael@0 5016 // Success indicates we passed ProxyFailover, in that case we must not continue
michael@0 5017 // with this code chain.
michael@0 5018 if (NS_SUCCEEDED(result))
michael@0 5019 return NS_OK;
michael@0 5020
michael@0 5021 return ContinueOnStartRequest2(result);
michael@0 5022 }
michael@0 5023
michael@0 5024 nsresult
michael@0 5025 nsHttpChannel::ContinueOnStartRequest2(nsresult result)
michael@0 5026 {
michael@0 5027 // on other request errors, try to fall back
michael@0 5028 if (NS_FAILED(mStatus)) {
michael@0 5029 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
michael@0 5030 bool waitingForRedirectCallback;
michael@0 5031 ProcessFallback(&waitingForRedirectCallback);
michael@0 5032 if (waitingForRedirectCallback)
michael@0 5033 return NS_OK;
michael@0 5034 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
michael@0 5035 }
michael@0 5036
michael@0 5037 return ContinueOnStartRequest3(NS_OK);
michael@0 5038 }
michael@0 5039
michael@0 5040 nsresult
michael@0 5041 nsHttpChannel::ContinueOnStartRequest3(nsresult result)
michael@0 5042 {
michael@0 5043 if (mFallingBack)
michael@0 5044 return NS_OK;
michael@0 5045
michael@0 5046 return CallOnStartRequest();
michael@0 5047 }
michael@0 5048
michael@0 5049 NS_IMETHODIMP
michael@0 5050 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
michael@0 5051 {
michael@0 5052 PROFILER_LABEL("network", "nsHttpChannel::OnStopRequest");
michael@0 5053 LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
michael@0 5054 this, request, status));
michael@0 5055
michael@0 5056 if (mTimingEnabled && request == mCachePump) {
michael@0 5057 mCacheReadEnd = TimeStamp::Now();
michael@0 5058 }
michael@0 5059
michael@0 5060 // allow content to be cached if it was loaded successfully (bug #482935)
michael@0 5061 bool contentComplete = NS_SUCCEEDED(status);
michael@0 5062
michael@0 5063 // honor the cancelation status even if the underlying transaction completed.
michael@0 5064 if (mCanceled || NS_FAILED(mStatus))
michael@0 5065 status = mStatus;
michael@0 5066
michael@0 5067 if (mCachedContentIsPartial) {
michael@0 5068 if (NS_SUCCEEDED(status)) {
michael@0 5069 // mTransactionPump should be suspended
michael@0 5070 MOZ_ASSERT(request != mTransactionPump,
michael@0 5071 "byte-range transaction finished prematurely");
michael@0 5072
michael@0 5073 if (request == mCachePump) {
michael@0 5074 bool streamDone;
michael@0 5075 status = OnDoneReadingPartialCacheEntry(&streamDone);
michael@0 5076 if (NS_SUCCEEDED(status) && !streamDone)
michael@0 5077 return status;
michael@0 5078 // otherwise, fall through and fire OnStopRequest...
michael@0 5079 }
michael@0 5080 else if (request == mTransactionPump) {
michael@0 5081 MOZ_ASSERT(mConcurentCacheAccess);
michael@0 5082 }
michael@0 5083 else
michael@0 5084 NS_NOTREACHED("unexpected request");
michael@0 5085 }
michael@0 5086 // Do not to leave the transaction in a suspended state in error cases.
michael@0 5087 if (NS_FAILED(status) && mTransaction)
michael@0 5088 gHttpHandler->CancelTransaction(mTransaction, status);
michael@0 5089 }
michael@0 5090
michael@0 5091 if (mTransaction) {
michael@0 5092 // determine if we should call DoAuthRetry
michael@0 5093 bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
michael@0 5094
michael@0 5095 //
michael@0 5096 // grab references to connection in case we need to retry an
michael@0 5097 // authentication request over it or use it for an upgrade
michael@0 5098 // to another protocol.
michael@0 5099 //
michael@0 5100 // this code relies on the code in nsHttpTransaction::Close, which
michael@0 5101 // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
michael@0 5102 // keep the connection around after the transaction is finished.
michael@0 5103 //
michael@0 5104 nsRefPtr<nsAHttpConnection> conn;
michael@0 5105 if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION)) {
michael@0 5106 conn = mTransaction->GetConnectionReference();
michael@0 5107 // This is so far a workaround to fix leak when reusing unpersistent
michael@0 5108 // connection for authentication retry. See bug 459620 comment 4
michael@0 5109 // for details.
michael@0 5110 if (conn && !conn->IsPersistent())
michael@0 5111 conn = nullptr;
michael@0 5112 }
michael@0 5113
michael@0 5114 nsRefPtr<nsAHttpConnection> stickyConn;
michael@0 5115 if (mCaps & NS_HTTP_STICKY_CONNECTION)
michael@0 5116 stickyConn = mTransaction->GetConnectionReference();
michael@0 5117
michael@0 5118 // at this point, we're done with the transaction
michael@0 5119 mTransactionTimings = mTransaction->Timings();
michael@0 5120 mTransaction = nullptr;
michael@0 5121 mTransactionPump = nullptr;
michael@0 5122
michael@0 5123 // We no longer need the dns prefetch object
michael@0 5124 if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) {
michael@0 5125 mTransactionTimings.domainLookupStart =
michael@0 5126 mDNSPrefetch->StartTimestamp();
michael@0 5127 mTransactionTimings.domainLookupEnd =
michael@0 5128 mDNSPrefetch->EndTimestamp();
michael@0 5129 }
michael@0 5130 mDNSPrefetch = nullptr;
michael@0 5131
michael@0 5132 // handle auth retry...
michael@0 5133 if (authRetry) {
michael@0 5134 mAuthRetryPending = false;
michael@0 5135 status = DoAuthRetry(conn);
michael@0 5136 if (NS_SUCCEEDED(status))
michael@0 5137 return NS_OK;
michael@0 5138 }
michael@0 5139
michael@0 5140 // If DoAuthRetry failed, or if we have been cancelled since showing
michael@0 5141 // the auth. dialog, then we need to send OnStartRequest now
michael@0 5142 if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
michael@0 5143 MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
michael@0 5144 // NOTE: since we have a failure status, we can ignore the return
michael@0 5145 // value from onStartRequest.
michael@0 5146 if (mListener) {
michael@0 5147 mListener->OnStartRequest(this, mListenerContext);
michael@0 5148 } else {
michael@0 5149 NS_WARNING("OnStartRequest skipped because of null listener");
michael@0 5150 }
michael@0 5151 }
michael@0 5152
michael@0 5153 // if this transaction has been replaced, then bail.
michael@0 5154 if (mTransactionReplaced)
michael@0 5155 return NS_OK;
michael@0 5156
michael@0 5157 if (mUpgradeProtocolCallback && stickyConn &&
michael@0 5158 mResponseHead && mResponseHead->Status() == 101) {
michael@0 5159 gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn,
michael@0 5160 mUpgradeProtocolCallback);
michael@0 5161 }
michael@0 5162 }
michael@0 5163
michael@0 5164 mIsPending = false;
michael@0 5165
michael@0 5166 // if needed, check cache entry has all data we expect
michael@0 5167 if (mCacheEntry && mCachePump &&
michael@0 5168 mConcurentCacheAccess && contentComplete) {
michael@0 5169 int64_t size, contentLength;
michael@0 5170 nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
michael@0 5171 if (NS_SUCCEEDED(rv)) {
michael@0 5172 if (size == int64_t(-1)) {
michael@0 5173 // mayhemer TODO - we have to restart read from cache here at the size offset
michael@0 5174 MOZ_ASSERT(false);
michael@0 5175 LOG((" cache entry write is still in progress, but we just "
michael@0 5176 "finished reading the cache entry"));
michael@0 5177 }
michael@0 5178 else if (contentLength != int64_t(-1) && contentLength != size) {
michael@0 5179 LOG((" concurrent cache entry write has been interrupted"));
michael@0 5180 mCachedResponseHead = mResponseHead;
michael@0 5181 rv = MaybeSetupByteRangeRequest(size, contentLength);
michael@0 5182 if (NS_SUCCEEDED(rv) && mIsPartialRequest) {
michael@0 5183 // Prevent read from cache again
michael@0 5184 mCachedContentIsValid = 0;
michael@0 5185 mCachedContentIsPartial = 1;
michael@0 5186
michael@0 5187 // Perform the range request
michael@0 5188 rv = ContinueConnect();
michael@0 5189 if (NS_SUCCEEDED(rv)) {
michael@0 5190 LOG((" performing range request"));
michael@0 5191 mCachePump = nullptr;
michael@0 5192 return NS_OK;
michael@0 5193 } else {
michael@0 5194 LOG((" but range request perform failed 0x%08x", rv));
michael@0 5195 status = NS_ERROR_NET_INTERRUPT;
michael@0 5196 }
michael@0 5197 }
michael@0 5198 else {
michael@0 5199 LOG((" but range request setup failed rv=0x%08x, failing load", rv));
michael@0 5200 }
michael@0 5201 }
michael@0 5202 }
michael@0 5203 }
michael@0 5204
michael@0 5205 mStatus = status;
michael@0 5206
michael@0 5207 // perform any final cache operations before we close the cache entry.
michael@0 5208 if (mCacheEntry && mRequestTimeInitialized) {
michael@0 5209 bool writeAccess;
michael@0 5210 // New implementation just returns value of the !mCacheEntryIsReadOnly flag passed in.
michael@0 5211 // Old implementation checks on nsICache::ACCESS_WRITE flag.
michael@0 5212 mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
michael@0 5213 if (writeAccess) {
michael@0 5214 FinalizeCacheEntry();
michael@0 5215 }
michael@0 5216 }
michael@0 5217
michael@0 5218 // Register entry to the Performance resource timing
michael@0 5219 nsPerformance* documentPerformance = GetPerformance();
michael@0 5220 if (documentPerformance) {
michael@0 5221 documentPerformance->AddEntry(this, this);
michael@0 5222 }
michael@0 5223
michael@0 5224 if (mListener) {
michael@0 5225 LOG((" calling OnStopRequest\n"));
michael@0 5226 mListener->OnStopRequest(this, mListenerContext, status);
michael@0 5227 }
michael@0 5228
michael@0 5229 MOZ_EVENT_TRACER_DONE(this, "net::http::channel");
michael@0 5230
michael@0 5231 CloseCacheEntry(!contentComplete);
michael@0 5232
michael@0 5233 if (mOfflineCacheEntry)
michael@0 5234 CloseOfflineCacheEntry();
michael@0 5235
michael@0 5236 if (mLoadGroup)
michael@0 5237 mLoadGroup->RemoveRequest(this, nullptr, status);
michael@0 5238
michael@0 5239 // We don't need this info anymore
michael@0 5240 CleanRedirectCacheChainIfNecessary();
michael@0 5241
michael@0 5242 ReleaseListeners();
michael@0 5243
michael@0 5244 return NS_OK;
michael@0 5245 }
michael@0 5246
michael@0 5247 //-----------------------------------------------------------------------------
michael@0 5248 // nsHttpChannel::nsIStreamListener
michael@0 5249 //-----------------------------------------------------------------------------
michael@0 5250
michael@0 5251 class OnTransportStatusAsyncEvent : public nsRunnable
michael@0 5252 {
michael@0 5253 public:
michael@0 5254 OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
michael@0 5255 nsresult aTransportStatus,
michael@0 5256 uint64_t aProgress,
michael@0 5257 uint64_t aProgressMax)
michael@0 5258 : mEventSink(aEventSink)
michael@0 5259 , mTransportStatus(aTransportStatus)
michael@0 5260 , mProgress(aProgress)
michael@0 5261 , mProgressMax(aProgressMax)
michael@0 5262 {
michael@0 5263 MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
michael@0 5264 }
michael@0 5265
michael@0 5266 NS_IMETHOD Run()
michael@0 5267 {
michael@0 5268 MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
michael@0 5269 if (mEventSink) {
michael@0 5270 mEventSink->OnTransportStatus(nullptr, mTransportStatus,
michael@0 5271 mProgress, mProgressMax);
michael@0 5272 }
michael@0 5273 return NS_OK;
michael@0 5274 }
michael@0 5275 private:
michael@0 5276 nsCOMPtr<nsITransportEventSink> mEventSink;
michael@0 5277 nsresult mTransportStatus;
michael@0 5278 uint64_t mProgress;
michael@0 5279 uint64_t mProgressMax;
michael@0 5280 };
michael@0 5281
michael@0 5282 NS_IMETHODIMP
michael@0 5283 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
michael@0 5284 nsIInputStream *input,
michael@0 5285 uint64_t offset, uint32_t count)
michael@0 5286 {
michael@0 5287 PROFILER_LABEL("network", "nsHttpChannel::OnDataAvailable");
michael@0 5288 LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%llu count=%u]\n",
michael@0 5289 this, request, offset, count));
michael@0 5290
michael@0 5291 // don't send out OnDataAvailable notifications if we've been canceled.
michael@0 5292 if (mCanceled)
michael@0 5293 return mStatus;
michael@0 5294
michael@0 5295 MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
michael@0 5296
michael@0 5297 MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
michael@0 5298 "transaction pump not suspended");
michael@0 5299
michael@0 5300 if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
michael@0 5301 uint32_t n;
michael@0 5302 return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
michael@0 5303 }
michael@0 5304
michael@0 5305 if (mListener) {
michael@0 5306 //
michael@0 5307 // synthesize transport progress event. we do this here since we want
michael@0 5308 // to delay OnProgress events until we start streaming data. this is
michael@0 5309 // crucially important since it impacts the lock icon (see bug 240053).
michael@0 5310 //
michael@0 5311 nsresult transportStatus;
michael@0 5312 if (request == mCachePump)
michael@0 5313 transportStatus = NS_NET_STATUS_READING;
michael@0 5314 else
michael@0 5315 transportStatus = NS_NET_STATUS_RECEIVING_FROM;
michael@0 5316
michael@0 5317 // mResponseHead may reference new or cached headers, but either way it
michael@0 5318 // holds our best estimate of the total content length. Even in the case
michael@0 5319 // of a byte range request, the content length stored in the cached
michael@0 5320 // response headers is what we want to use here.
michael@0 5321
michael@0 5322 uint64_t progressMax(uint64_t(mResponseHead->ContentLength()));
michael@0 5323 uint64_t progress = mLogicalOffset + uint64_t(count);
michael@0 5324
michael@0 5325 if (progress > progressMax)
michael@0 5326 NS_WARNING("unexpected progress values - "
michael@0 5327 "is server exceeding content length?");
michael@0 5328
michael@0 5329 if (NS_IsMainThread()) {
michael@0 5330 OnTransportStatus(nullptr, transportStatus, progress, progressMax);
michael@0 5331 } else {
michael@0 5332 nsresult rv = NS_DispatchToMainThread(
michael@0 5333 new OnTransportStatusAsyncEvent(this, transportStatus,
michael@0 5334 progress, progressMax));
michael@0 5335 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5336 }
michael@0 5337
michael@0 5338 //
michael@0 5339 // we have to manually keep the logical offset of the stream up-to-date.
michael@0 5340 // we cannot depend solely on the offset provided, since we may have
michael@0 5341 // already streamed some data from another source (see, for example,
michael@0 5342 // OnDoneReadingPartialCacheEntry).
michael@0 5343 //
michael@0 5344 if (!mLogicalOffset)
michael@0 5345 MOZ_EVENT_TRACER_EXEC(this, "net::http::channel");
michael@0 5346
michael@0 5347 int64_t offsetBefore = 0;
michael@0 5348 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
michael@0 5349 if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
michael@0 5350 seekable = nullptr;
michael@0 5351 }
michael@0 5352
michael@0 5353 nsresult rv = mListener->OnDataAvailable(this,
michael@0 5354 mListenerContext,
michael@0 5355 input,
michael@0 5356 mLogicalOffset,
michael@0 5357 count);
michael@0 5358 if (NS_SUCCEEDED(rv)) {
michael@0 5359 // by contract mListener must read all of "count" bytes, but
michael@0 5360 // nsInputStreamPump is tolerant to seekable streams that violate that
michael@0 5361 // and it will redeliver incompletely read data. So we need to do
michael@0 5362 // the same thing when updating the progress counter to stay in sync.
michael@0 5363 int64_t offsetAfter, delta;
michael@0 5364 if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
michael@0 5365 delta = offsetAfter - offsetBefore;
michael@0 5366 if (delta != count) {
michael@0 5367 count = delta;
michael@0 5368
michael@0 5369 NS_WARNING("Listener OnDataAvailable contract violation");
michael@0 5370 nsCOMPtr<nsIConsoleService> consoleService =
michael@0 5371 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
michael@0 5372 nsAutoString message
michael@0 5373 (NS_LITERAL_STRING(
michael@0 5374 "http channel Listener OnDataAvailable contract violation"));
michael@0 5375 if (consoleService) {
michael@0 5376 consoleService->LogStringMessage(message.get());
michael@0 5377 }
michael@0 5378 }
michael@0 5379 }
michael@0 5380 mLogicalOffset += count;
michael@0 5381 }
michael@0 5382
michael@0 5383 return rv;
michael@0 5384 }
michael@0 5385
michael@0 5386 return NS_ERROR_ABORT;
michael@0 5387 }
michael@0 5388
michael@0 5389 //-----------------------------------------------------------------------------
michael@0 5390 // nsHttpChannel::nsIThreadRetargetableRequest
michael@0 5391 //-----------------------------------------------------------------------------
michael@0 5392
michael@0 5393 NS_IMETHODIMP
michael@0 5394 nsHttpChannel::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
michael@0 5395 {
michael@0 5396 MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
michael@0 5397
michael@0 5398 NS_ENSURE_ARG(aNewTarget);
michael@0 5399 if (aNewTarget == NS_GetCurrentThread()) {
michael@0 5400 NS_WARNING("Retargeting delivery to same thread");
michael@0 5401 return NS_OK;
michael@0 5402 }
michael@0 5403 NS_ENSURE_TRUE(mTransactionPump || mCachePump, NS_ERROR_NOT_AVAILABLE);
michael@0 5404
michael@0 5405 nsresult rv = NS_OK;
michael@0 5406 // If both cache pump and transaction pump exist, we're probably dealing
michael@0 5407 // with partially cached content. So, we must be able to retarget both.
michael@0 5408 nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
michael@0 5409 nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
michael@0 5410 if (mCachePump) {
michael@0 5411 retargetableCachePump = do_QueryObject(mCachePump);
michael@0 5412 // nsInputStreamPump should implement this interface.
michael@0 5413 MOZ_ASSERT(retargetableCachePump);
michael@0 5414 rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
michael@0 5415 }
michael@0 5416 if (NS_SUCCEEDED(rv) && mTransactionPump) {
michael@0 5417 retargetableTransactionPump = do_QueryObject(mTransactionPump);
michael@0 5418 // nsInputStreamPump should implement this interface.
michael@0 5419 MOZ_ASSERT(retargetableTransactionPump);
michael@0 5420 rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
michael@0 5421
michael@0 5422 // If retarget fails for transaction pump, we must restore mCachePump.
michael@0 5423 if (NS_FAILED(rv) && retargetableCachePump) {
michael@0 5424 nsCOMPtr<nsIThread> mainThread;
michael@0 5425 rv = NS_GetMainThread(getter_AddRefs(mainThread));
michael@0 5426 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5427 rv = retargetableCachePump->RetargetDeliveryTo(mainThread);
michael@0 5428 }
michael@0 5429 }
michael@0 5430 return rv;
michael@0 5431 }
michael@0 5432
michael@0 5433 //-----------------------------------------------------------------------------
michael@0 5434 // nsHttpChannel::nsThreadRetargetableStreamListener
michael@0 5435 //-----------------------------------------------------------------------------
michael@0 5436
michael@0 5437 NS_IMETHODIMP
michael@0 5438 nsHttpChannel::CheckListenerChain()
michael@0 5439 {
michael@0 5440 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
michael@0 5441 nsresult rv = NS_OK;
michael@0 5442 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
michael@0 5443 do_QueryInterface(mListener, &rv);
michael@0 5444 if (retargetableListener) {
michael@0 5445 rv = retargetableListener->CheckListenerChain();
michael@0 5446 }
michael@0 5447 return rv;
michael@0 5448 }
michael@0 5449
michael@0 5450 //-----------------------------------------------------------------------------
michael@0 5451 // nsHttpChannel::nsITransportEventSink
michael@0 5452 //-----------------------------------------------------------------------------
michael@0 5453
michael@0 5454 NS_IMETHODIMP
michael@0 5455 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
michael@0 5456 uint64_t progress, uint64_t progressMax)
michael@0 5457 {
michael@0 5458 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
michael@0 5459 // cache the progress sink so we don't have to query for it each time.
michael@0 5460 if (!mProgressSink)
michael@0 5461 GetCallback(mProgressSink);
michael@0 5462
michael@0 5463 if (status == NS_NET_STATUS_CONNECTED_TO ||
michael@0 5464 status == NS_NET_STATUS_WAITING_FOR) {
michael@0 5465 nsCOMPtr<nsISocketTransport> socketTransport =
michael@0 5466 do_QueryInterface(trans);
michael@0 5467 if (socketTransport) {
michael@0 5468 socketTransport->GetSelfAddr(&mSelfAddr);
michael@0 5469 socketTransport->GetPeerAddr(&mPeerAddr);
michael@0 5470 }
michael@0 5471 }
michael@0 5472
michael@0 5473 // block socket status event after Cancel or OnStopRequest has been called.
michael@0 5474 if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
michael@0 5475 LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n",
michael@0 5476 this, status, progress, progressMax));
michael@0 5477
michael@0 5478 nsAutoCString host;
michael@0 5479 mURI->GetHost(host);
michael@0 5480 mProgressSink->OnStatus(this, nullptr, status,
michael@0 5481 NS_ConvertUTF8toUTF16(host).get());
michael@0 5482
michael@0 5483 if (progress > 0) {
michael@0 5484 MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
michael@0 5485 // Try to get mProgressSink if it was nulled out during OnStatus.
michael@0 5486 if (!mProgressSink) {
michael@0 5487 GetCallback(mProgressSink);
michael@0 5488 }
michael@0 5489 if (mProgressSink) {
michael@0 5490 mProgressSink->OnProgress(this, nullptr, progress, progressMax);
michael@0 5491 }
michael@0 5492 }
michael@0 5493 }
michael@0 5494 #ifdef DEBUG
michael@0 5495 else
michael@0 5496 LOG(("skipping status notification [this=%p sink=%p pending=%u background=%x]\n",
michael@0 5497 this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
michael@0 5498 #endif
michael@0 5499
michael@0 5500 return NS_OK;
michael@0 5501 }
michael@0 5502
michael@0 5503 //-----------------------------------------------------------------------------
michael@0 5504 // nsHttpChannel::nsICacheInfoChannel
michael@0 5505 //-----------------------------------------------------------------------------
michael@0 5506
michael@0 5507 NS_IMETHODIMP
michael@0 5508 nsHttpChannel::IsFromCache(bool *value)
michael@0 5509 {
michael@0 5510 if (!mIsPending)
michael@0 5511 return NS_ERROR_NOT_AVAILABLE;
michael@0 5512
michael@0 5513 // return false if reading a partial cache entry; the data isn't entirely
michael@0 5514 // from the cache!
michael@0 5515
michael@0 5516 *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
michael@0 5517 mCachedContentIsValid && !mCachedContentIsPartial;
michael@0 5518
michael@0 5519 return NS_OK;
michael@0 5520 }
michael@0 5521
michael@0 5522 NS_IMETHODIMP
michael@0 5523 nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval)
michael@0 5524 {
michael@0 5525 NS_ENSURE_ARG_POINTER(_retval);
michael@0 5526 if (!mCacheEntry)
michael@0 5527 return NS_ERROR_NOT_AVAILABLE;
michael@0 5528
michael@0 5529 return mCacheEntry->GetExpirationTime(_retval);
michael@0 5530 }
michael@0 5531
michael@0 5532 NS_IMETHODIMP
michael@0 5533 nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval)
michael@0 5534 {
michael@0 5535 nsresult rv;
michael@0 5536
michael@0 5537 if (!mCacheEntry)
michael@0 5538 return NS_ERROR_NOT_AVAILABLE;
michael@0 5539
michael@0 5540 nsXPIDLCString cachedCharset;
michael@0 5541 rv = mCacheEntry->GetMetaDataElement("charset",
michael@0 5542 getter_Copies(cachedCharset));
michael@0 5543 if (NS_SUCCEEDED(rv))
michael@0 5544 _retval = cachedCharset;
michael@0 5545
michael@0 5546 return rv;
michael@0 5547 }
michael@0 5548
michael@0 5549 NS_IMETHODIMP
michael@0 5550 nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset)
michael@0 5551 {
michael@0 5552 if (!mCacheEntry)
michael@0 5553 return NS_ERROR_NOT_AVAILABLE;
michael@0 5554
michael@0 5555 return mCacheEntry->SetMetaDataElement("charset",
michael@0 5556 PromiseFlatCString(aCharset).get());
michael@0 5557 }
michael@0 5558
michael@0 5559
michael@0 5560 NS_IMETHODIMP
michael@0 5561 nsHttpChannel::GetCacheDomain(nsACString &value)
michael@0 5562 {
michael@0 5563 value = mCacheDomain;
michael@0 5564
michael@0 5565 return NS_OK;
michael@0 5566 }
michael@0 5567
michael@0 5568 NS_IMETHODIMP
michael@0 5569 nsHttpChannel::SetCacheDomain(const nsACString &value)
michael@0 5570 {
michael@0 5571 mCacheDomain = value;
michael@0 5572
michael@0 5573 return NS_OK;
michael@0 5574 }
michael@0 5575
michael@0 5576 //-----------------------------------------------------------------------------
michael@0 5577 // nsHttpChannel::nsICachingChannel
michael@0 5578 //-----------------------------------------------------------------------------
michael@0 5579
michael@0 5580 NS_IMETHODIMP
michael@0 5581 nsHttpChannel::GetCacheToken(nsISupports **token)
michael@0 5582 {
michael@0 5583 NS_ENSURE_ARG_POINTER(token);
michael@0 5584 if (!mCacheEntry)
michael@0 5585 return NS_ERROR_NOT_AVAILABLE;
michael@0 5586 return CallQueryInterface(mCacheEntry, token);
michael@0 5587 }
michael@0 5588
michael@0 5589 NS_IMETHODIMP
michael@0 5590 nsHttpChannel::SetCacheToken(nsISupports *token)
michael@0 5591 {
michael@0 5592 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 5593 }
michael@0 5594
michael@0 5595 NS_IMETHODIMP
michael@0 5596 nsHttpChannel::GetOfflineCacheToken(nsISupports **token)
michael@0 5597 {
michael@0 5598 NS_ENSURE_ARG_POINTER(token);
michael@0 5599 if (!mOfflineCacheEntry)
michael@0 5600 return NS_ERROR_NOT_AVAILABLE;
michael@0 5601 return CallQueryInterface(mOfflineCacheEntry, token);
michael@0 5602 }
michael@0 5603
michael@0 5604 NS_IMETHODIMP
michael@0 5605 nsHttpChannel::SetOfflineCacheToken(nsISupports *token)
michael@0 5606 {
michael@0 5607 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 5608 }
michael@0 5609
michael@0 5610 class nsHttpChannelCacheKey MOZ_FINAL : public nsISupportsPRUint32,
michael@0 5611 public nsISupportsCString
michael@0 5612 {
michael@0 5613 NS_DECL_ISUPPORTS
michael@0 5614
michael@0 5615 NS_DECL_NSISUPPORTSPRIMITIVE
michael@0 5616 NS_FORWARD_NSISUPPORTSPRUINT32(mSupportsPRUint32->)
michael@0 5617
michael@0 5618 // Both interfaces declares toString method with the same signature.
michael@0 5619 // Thus we have to delegate only to nsISupportsPRUint32 implementation.
michael@0 5620 NS_IMETHOD GetData(nsACString & aData)
michael@0 5621 {
michael@0 5622 return mSupportsCString->GetData(aData);
michael@0 5623 }
michael@0 5624 NS_IMETHOD SetData(const nsACString & aData)
michael@0 5625 {
michael@0 5626 return mSupportsCString->SetData(aData);
michael@0 5627 }
michael@0 5628
michael@0 5629 public:
michael@0 5630 nsresult SetData(uint32_t aPostID, const nsACString& aKey);
michael@0 5631
michael@0 5632 protected:
michael@0 5633 nsCOMPtr<nsISupportsPRUint32> mSupportsPRUint32;
michael@0 5634 nsCOMPtr<nsISupportsCString> mSupportsCString;
michael@0 5635 };
michael@0 5636
michael@0 5637 NS_IMPL_ADDREF(nsHttpChannelCacheKey)
michael@0 5638 NS_IMPL_RELEASE(nsHttpChannelCacheKey)
michael@0 5639 NS_INTERFACE_TABLE_HEAD(nsHttpChannelCacheKey)
michael@0 5640 NS_INTERFACE_TABLE_BEGIN
michael@0 5641 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
michael@0 5642 nsISupports, nsISupportsPRUint32)
michael@0 5643 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
michael@0 5644 nsISupportsPrimitive, nsISupportsPRUint32)
michael@0 5645 NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
michael@0 5646 nsISupportsPRUint32)
michael@0 5647 NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
michael@0 5648 nsISupportsCString)
michael@0 5649 NS_INTERFACE_TABLE_END
michael@0 5650 NS_INTERFACE_TABLE_TAIL
michael@0 5651
michael@0 5652 NS_IMETHODIMP nsHttpChannelCacheKey::GetType(uint16_t *aType)
michael@0 5653 {
michael@0 5654 NS_ENSURE_ARG_POINTER(aType);
michael@0 5655
michael@0 5656 *aType = TYPE_PRUINT32;
michael@0 5657 return NS_OK;
michael@0 5658 }
michael@0 5659
michael@0 5660 nsresult nsHttpChannelCacheKey::SetData(uint32_t aPostID,
michael@0 5661 const nsACString& aKey)
michael@0 5662 {
michael@0 5663 nsresult rv;
michael@0 5664
michael@0 5665 mSupportsCString =
michael@0 5666 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
michael@0 5667 if (NS_FAILED(rv)) return rv;
michael@0 5668
michael@0 5669 mSupportsCString->SetData(aKey);
michael@0 5670 if (NS_FAILED(rv)) return rv;
michael@0 5671
michael@0 5672 mSupportsPRUint32 =
michael@0 5673 do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
michael@0 5674 if (NS_FAILED(rv)) return rv;
michael@0 5675
michael@0 5676 mSupportsPRUint32->SetData(aPostID);
michael@0 5677 if (NS_FAILED(rv)) return rv;
michael@0 5678
michael@0 5679 return NS_OK;
michael@0 5680 }
michael@0 5681
michael@0 5682 NS_IMETHODIMP
michael@0 5683 nsHttpChannel::GetCacheKey(nsISupports **key)
michael@0 5684 {
michael@0 5685 // mayhemer: TODO - do we need this API?
michael@0 5686
michael@0 5687 nsresult rv;
michael@0 5688 NS_ENSURE_ARG_POINTER(key);
michael@0 5689
michael@0 5690 LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
michael@0 5691
michael@0 5692 *key = nullptr;
michael@0 5693
michael@0 5694 nsRefPtr<nsHttpChannelCacheKey> container =
michael@0 5695 new nsHttpChannelCacheKey();
michael@0 5696
michael@0 5697 if (!container)
michael@0 5698 return NS_ERROR_OUT_OF_MEMORY;
michael@0 5699
michael@0 5700 nsAutoCString cacheKey;
michael@0 5701 rv = GenerateCacheKey(mPostID, cacheKey);
michael@0 5702 if (NS_FAILED(rv)) return rv;
michael@0 5703
michael@0 5704 rv = container->SetData(mPostID, cacheKey);
michael@0 5705 if (NS_FAILED(rv)) return rv;
michael@0 5706
michael@0 5707 return CallQueryInterface(container.get(), key);
michael@0 5708 }
michael@0 5709
michael@0 5710 NS_IMETHODIMP
michael@0 5711 nsHttpChannel::SetCacheKey(nsISupports *key)
michael@0 5712 {
michael@0 5713 nsresult rv;
michael@0 5714
michael@0 5715 LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
michael@0 5716
michael@0 5717 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 5718
michael@0 5719 if (!key)
michael@0 5720 mPostID = 0;
michael@0 5721 else {
michael@0 5722 // extract the post id
michael@0 5723 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
michael@0 5724 if (NS_FAILED(rv)) return rv;
michael@0 5725
michael@0 5726 rv = container->GetData(&mPostID);
michael@0 5727 if (NS_FAILED(rv)) return rv;
michael@0 5728 }
michael@0 5729 return NS_OK;
michael@0 5730 }
michael@0 5731
michael@0 5732 //-----------------------------------------------------------------------------
michael@0 5733 // nsHttpChannel::nsIResumableChannel
michael@0 5734 //-----------------------------------------------------------------------------
michael@0 5735
michael@0 5736 NS_IMETHODIMP
michael@0 5737 nsHttpChannel::ResumeAt(uint64_t aStartPos,
michael@0 5738 const nsACString& aEntityID)
michael@0 5739 {
michael@0 5740 LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n",
michael@0 5741 this, aStartPos, PromiseFlatCString(aEntityID).get()));
michael@0 5742 mEntityID = aEntityID;
michael@0 5743 mStartPos = aStartPos;
michael@0 5744 mResuming = true;
michael@0 5745 return NS_OK;
michael@0 5746 }
michael@0 5747
michael@0 5748 nsresult
michael@0 5749 nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
michael@0 5750 {
michael@0 5751 LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
michael@0 5752
michael@0 5753 MOZ_ASSERT(!mTransaction, "should not have a transaction");
michael@0 5754 nsresult rv;
michael@0 5755
michael@0 5756 // toggle mIsPending to allow nsIObserver implementations to modify
michael@0 5757 // the request headers (bug 95044).
michael@0 5758 mIsPending = false;
michael@0 5759
michael@0 5760 // fetch cookies, and add them to the request header.
michael@0 5761 // the server response could have included cookies that must be sent with
michael@0 5762 // this authentication attempt (bug 84794).
michael@0 5763 // TODO: save cookies from auth response and send them here (bug 572151).
michael@0 5764 AddCookiesToRequest();
michael@0 5765
michael@0 5766 // notify "http-on-modify-request" observers
michael@0 5767 CallOnModifyRequestObservers();
michael@0 5768
michael@0 5769 mIsPending = true;
michael@0 5770
michael@0 5771 // get rid of the old response headers
michael@0 5772 mResponseHead = nullptr;
michael@0 5773
michael@0 5774 // rewind the upload stream
michael@0 5775 if (mUploadStream) {
michael@0 5776 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
michael@0 5777 if (seekable)
michael@0 5778 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
michael@0 5779 }
michael@0 5780
michael@0 5781 // set sticky connection flag and disable pipelining.
michael@0 5782 mCaps |= NS_HTTP_STICKY_CONNECTION;
michael@0 5783 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
michael@0 5784
michael@0 5785 // and create a new one...
michael@0 5786 rv = SetupTransaction();
michael@0 5787 if (NS_FAILED(rv)) return rv;
michael@0 5788
michael@0 5789 // transfer ownership of connection to transaction
michael@0 5790 if (conn)
michael@0 5791 mTransaction->SetConnection(conn);
michael@0 5792
michael@0 5793 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
michael@0 5794 if (NS_FAILED(rv)) return rv;
michael@0 5795
michael@0 5796 rv = mTransactionPump->AsyncRead(this, nullptr);
michael@0 5797 if (NS_FAILED(rv)) return rv;
michael@0 5798
michael@0 5799 uint32_t suspendCount = mSuspendCount;
michael@0 5800 while (suspendCount--)
michael@0 5801 mTransactionPump->Suspend();
michael@0 5802
michael@0 5803 return NS_OK;
michael@0 5804 }
michael@0 5805
michael@0 5806 //-----------------------------------------------------------------------------
michael@0 5807 // nsHttpChannel::nsIApplicationCacheChannel
michael@0 5808 //-----------------------------------------------------------------------------
michael@0 5809
michael@0 5810 NS_IMETHODIMP
michael@0 5811 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
michael@0 5812 {
michael@0 5813 NS_IF_ADDREF(*out = mApplicationCache);
michael@0 5814 return NS_OK;
michael@0 5815 }
michael@0 5816
michael@0 5817 NS_IMETHODIMP
michael@0 5818 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
michael@0 5819 {
michael@0 5820 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 5821
michael@0 5822 mApplicationCache = appCache;
michael@0 5823 return NS_OK;
michael@0 5824 }
michael@0 5825
michael@0 5826 NS_IMETHODIMP
michael@0 5827 nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out)
michael@0 5828 {
michael@0 5829 NS_IF_ADDREF(*out = mApplicationCacheForWrite);
michael@0 5830 return NS_OK;
michael@0 5831 }
michael@0 5832
michael@0 5833 NS_IMETHODIMP
michael@0 5834 nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache)
michael@0 5835 {
michael@0 5836 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 5837
michael@0 5838 mApplicationCacheForWrite = appCache;
michael@0 5839 return NS_OK;
michael@0 5840 }
michael@0 5841
michael@0 5842 NS_IMETHODIMP
michael@0 5843 nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
michael@0 5844 {
michael@0 5845 *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
michael@0 5846 return NS_OK;
michael@0 5847 }
michael@0 5848
michael@0 5849 NS_IMETHODIMP
michael@0 5850 nsHttpChannel::GetInheritApplicationCache(bool *aInherit)
michael@0 5851 {
michael@0 5852 *aInherit = mInheritApplicationCache;
michael@0 5853 return NS_OK;
michael@0 5854 }
michael@0 5855
michael@0 5856 NS_IMETHODIMP
michael@0 5857 nsHttpChannel::SetInheritApplicationCache(bool aInherit)
michael@0 5858 {
michael@0 5859 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 5860
michael@0 5861 mInheritApplicationCache = aInherit;
michael@0 5862 return NS_OK;
michael@0 5863 }
michael@0 5864
michael@0 5865 NS_IMETHODIMP
michael@0 5866 nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
michael@0 5867 {
michael@0 5868 *aChoose = mChooseApplicationCache;
michael@0 5869 return NS_OK;
michael@0 5870 }
michael@0 5871
michael@0 5872 NS_IMETHODIMP
michael@0 5873 nsHttpChannel::SetChooseApplicationCache(bool aChoose)
michael@0 5874 {
michael@0 5875 ENSURE_CALLED_BEFORE_CONNECT();
michael@0 5876
michael@0 5877 mChooseApplicationCache = aChoose;
michael@0 5878 return NS_OK;
michael@0 5879 }
michael@0 5880
michael@0 5881 nsHttpChannel::OfflineCacheEntryAsForeignMarker*
michael@0 5882 nsHttpChannel::GetOfflineCacheEntryAsForeignMarker()
michael@0 5883 {
michael@0 5884 if (!mApplicationCache)
michael@0 5885 return nullptr;
michael@0 5886
michael@0 5887 return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
michael@0 5888 }
michael@0 5889
michael@0 5890 nsresult
michael@0 5891 nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign()
michael@0 5892 {
michael@0 5893 nsresult rv;
michael@0 5894
michael@0 5895 nsCOMPtr<nsIURI> noRefURI;
michael@0 5896 rv = mCacheURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
michael@0 5897 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5898
michael@0 5899 nsAutoCString spec;
michael@0 5900 rv = noRefURI->GetAsciiSpec(spec);
michael@0 5901 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5902
michael@0 5903 return mApplicationCache->MarkEntry(spec,
michael@0 5904 nsIApplicationCache::ITEM_FOREIGN);
michael@0 5905 }
michael@0 5906
michael@0 5907 NS_IMETHODIMP
michael@0 5908 nsHttpChannel::MarkOfflineCacheEntryAsForeign()
michael@0 5909 {
michael@0 5910 nsresult rv;
michael@0 5911
michael@0 5912 nsAutoPtr<OfflineCacheEntryAsForeignMarker> marker(
michael@0 5913 GetOfflineCacheEntryAsForeignMarker());
michael@0 5914
michael@0 5915 if (!marker)
michael@0 5916 return NS_ERROR_NOT_AVAILABLE;
michael@0 5917
michael@0 5918 rv = marker->MarkAsForeign();
michael@0 5919 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5920
michael@0 5921 return NS_OK;
michael@0 5922 }
michael@0 5923
michael@0 5924 //-----------------------------------------------------------------------------
michael@0 5925 // nsHttpChannel::nsIAsyncVerifyRedirectCallback
michael@0 5926 //-----------------------------------------------------------------------------
michael@0 5927
michael@0 5928 nsresult
michael@0 5929 nsHttpChannel::WaitForRedirectCallback()
michael@0 5930 {
michael@0 5931 nsresult rv;
michael@0 5932 LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
michael@0 5933
michael@0 5934 if (mTransactionPump) {
michael@0 5935 rv = mTransactionPump->Suspend();
michael@0 5936 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5937 }
michael@0 5938 if (mCachePump) {
michael@0 5939 rv = mCachePump->Suspend();
michael@0 5940 if (NS_FAILED(rv) && mTransactionPump) {
michael@0 5941 #ifdef DEBUG
michael@0 5942 nsresult resume =
michael@0 5943 #endif
michael@0 5944 mTransactionPump->Resume();
michael@0 5945 MOZ_ASSERT(NS_SUCCEEDED(resume),
michael@0 5946 "Failed to resume transaction pump");
michael@0 5947 }
michael@0 5948 NS_ENSURE_SUCCESS(rv, rv);
michael@0 5949 }
michael@0 5950
michael@0 5951 mWaitingForRedirectCallback = true;
michael@0 5952 return NS_OK;
michael@0 5953 }
michael@0 5954
michael@0 5955 NS_IMETHODIMP
michael@0 5956 nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
michael@0 5957 {
michael@0 5958 LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
michael@0 5959 "result=%x stack=%d mWaitingForRedirectCallback=%u\n",
michael@0 5960 this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback));
michael@0 5961 MOZ_ASSERT(mWaitingForRedirectCallback,
michael@0 5962 "Someone forgot to call WaitForRedirectCallback() ?!");
michael@0 5963 mWaitingForRedirectCallback = false;
michael@0 5964
michael@0 5965 if (mCanceled && NS_SUCCEEDED(result))
michael@0 5966 result = NS_BINDING_ABORTED;
michael@0 5967
michael@0 5968 for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
michael@0 5969 --i;
michael@0 5970 // Pop the last function pushed to the stack
michael@0 5971 nsContinueRedirectionFunc func = mRedirectFuncStack[i];
michael@0 5972 mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
michael@0 5973
michael@0 5974 // Call it with the result we got from the callback or the deeper
michael@0 5975 // function call.
michael@0 5976 result = (this->*func)(result);
michael@0 5977
michael@0 5978 // If a new function has been pushed to the stack and placed us in the
michael@0 5979 // waiting state, we need to break the chain and wait for the callback
michael@0 5980 // again.
michael@0 5981 if (mWaitingForRedirectCallback)
michael@0 5982 break;
michael@0 5983 }
michael@0 5984
michael@0 5985 if (NS_FAILED(result) && !mCanceled) {
michael@0 5986 // First, cancel this channel if we are in failure state to set mStatus
michael@0 5987 // and let it be propagated to pumps.
michael@0 5988 Cancel(result);
michael@0 5989 }
michael@0 5990
michael@0 5991 if (!mWaitingForRedirectCallback) {
michael@0 5992 // We are not waiting for the callback. At this moment we must release
michael@0 5993 // reference to the redirect target channel, otherwise we may leak.
michael@0 5994 mRedirectChannel = nullptr;
michael@0 5995 MOZ_EVENT_TRACER_DONE(this, "net::http::channel");
michael@0 5996 }
michael@0 5997
michael@0 5998 // We always resume the pumps here. If all functions on stack have been
michael@0 5999 // called we need OnStopRequest to be triggered, and if we broke out of the
michael@0 6000 // loop above (and are thus waiting for a new callback) the suspension
michael@0 6001 // count must be balanced in the pumps.
michael@0 6002 if (mTransactionPump)
michael@0 6003 mTransactionPump->Resume();
michael@0 6004 if (mCachePump)
michael@0 6005 mCachePump->Resume();
michael@0 6006
michael@0 6007 return result;
michael@0 6008 }
michael@0 6009
michael@0 6010 void
michael@0 6011 nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func)
michael@0 6012 {
michael@0 6013 mRedirectFuncStack.AppendElement(func);
michael@0 6014 }
michael@0 6015
michael@0 6016 void
michael@0 6017 nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func)
michael@0 6018 {
michael@0 6019 MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
michael@0 6020 "Trying to pop wrong method from redirect async stack!");
michael@0 6021
michael@0 6022 mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
michael@0 6023 }
michael@0 6024
michael@0 6025 //-----------------------------------------------------------------------------
michael@0 6026 // nsIDNSListener functions
michael@0 6027 //-----------------------------------------------------------------------------
michael@0 6028
michael@0 6029 NS_IMETHODIMP
michael@0 6030 nsHttpChannel::OnLookupComplete(nsICancelable *request,
michael@0 6031 nsIDNSRecord *rec,
michael@0 6032 nsresult status)
michael@0 6033 {
michael@0 6034 MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
michael@0 6035
michael@0 6036 LOG(("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
michael@0 6037 "%s status[0x%x]\n",
michael@0 6038 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
michael@0 6039 NS_SUCCEEDED(status) ? "success" : "failure", status));
michael@0 6040
michael@0 6041 // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
michael@0 6042 // validly null if OnStopRequest has already been called.
michael@0 6043 if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) {
michael@0 6044 mTransactionTimings.domainLookupStart =
michael@0 6045 mDNSPrefetch->StartTimestamp();
michael@0 6046 mTransactionTimings.domainLookupEnd =
michael@0 6047 mDNSPrefetch->EndTimestamp();
michael@0 6048 }
michael@0 6049 mDNSPrefetch = nullptr;
michael@0 6050
michael@0 6051 // Unset DNS cache refresh if it was requested,
michael@0 6052 if (mCaps & NS_HTTP_REFRESH_DNS) {
michael@0 6053 mCaps &= ~NS_HTTP_REFRESH_DNS;
michael@0 6054 if (mTransaction) {
michael@0 6055 mTransaction->SetDNSWasRefreshed();
michael@0 6056 }
michael@0 6057 }
michael@0 6058
michael@0 6059 return NS_OK;
michael@0 6060 }
michael@0 6061
michael@0 6062 //-----------------------------------------------------------------------------
michael@0 6063 // nsHttpChannel internal functions
michael@0 6064 //-----------------------------------------------------------------------------
michael@0 6065
michael@0 6066 void
michael@0 6067 nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
michael@0 6068 {
michael@0 6069 // See RFC 2616 section 5.1.1. These are considered valid
michael@0 6070 // methods which DO NOT invalidate cache-entries for the
michael@0 6071 // referred resource. POST, PUT and DELETE as well as any
michael@0 6072 // other method not listed here will potentially invalidate
michael@0 6073 // any cached copy of the resource
michael@0 6074 if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
michael@0 6075 mRequestHead.IsHead() || mRequestHead.IsTrace() ||
michael@0 6076 mRequestHead.IsConnect()) {
michael@0 6077 return;
michael@0 6078 }
michael@0 6079
michael@0 6080 // Invalidate the request-uri.
michael@0 6081 #ifdef PR_LOGGING
michael@0 6082 nsAutoCString key;
michael@0 6083 mURI->GetAsciiSpec(key);
michael@0 6084 LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n",
michael@0 6085 this, key.get()));
michael@0 6086 #endif
michael@0 6087
michael@0 6088 DoInvalidateCacheEntry(mURI);
michael@0 6089
michael@0 6090 // Invalidate Location-header if set
michael@0 6091 const char *location = mResponseHead->PeekHeader(nsHttp::Location);
michael@0 6092 if (location) {
michael@0 6093 LOG((" Location-header=%s\n", location));
michael@0 6094 InvalidateCacheEntryForLocation(location);
michael@0 6095 }
michael@0 6096
michael@0 6097 // Invalidate Content-Location-header if set
michael@0 6098 location = mResponseHead->PeekHeader(nsHttp::Content_Location);
michael@0 6099 if (location) {
michael@0 6100 LOG((" Content-Location-header=%s\n", location));
michael@0 6101 InvalidateCacheEntryForLocation(location);
michael@0 6102 }
michael@0 6103 }
michael@0 6104
michael@0 6105 void
michael@0 6106 nsHttpChannel::InvalidateCacheEntryForLocation(const char *location)
michael@0 6107 {
michael@0 6108 nsAutoCString tmpCacheKey, tmpSpec;
michael@0 6109 nsCOMPtr<nsIURI> resultingURI;
michael@0 6110 nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
michael@0 6111 if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
michael@0 6112 DoInvalidateCacheEntry(resultingURI);
michael@0 6113 } else {
michael@0 6114 LOG((" hosts not matching\n"));
michael@0 6115 }
michael@0 6116 }
michael@0 6117
michael@0 6118 void
michael@0 6119 nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI)
michael@0 6120 {
michael@0 6121 // NOTE:
michael@0 6122 // Following comments 24,32 and 33 in bug #327765, we only care about
michael@0 6123 // the cache in the protocol-handler, not the application cache.
michael@0 6124 // The logic below deviates from the original logic in OpenCacheEntry on
michael@0 6125 // one point by using only READ_ONLY access-policy. I think this is safe.
michael@0 6126
michael@0 6127 nsresult rv;
michael@0 6128
michael@0 6129 #ifdef PR_LOGGING
michael@0 6130 nsAutoCString key;
michael@0 6131 aURI->GetAsciiSpec(key);
michael@0 6132 LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
michael@0 6133 #endif
michael@0 6134
michael@0 6135 nsCOMPtr<nsICacheStorageService> cacheStorageService =
michael@0 6136 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
michael@0 6137
michael@0 6138 nsCOMPtr<nsICacheStorage> cacheStorage;
michael@0 6139 if (NS_SUCCEEDED(rv)) {
michael@0 6140 nsRefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
michael@0 6141 rv = cacheStorageService->DiskCacheStorage(info, false, getter_AddRefs(cacheStorage));
michael@0 6142 }
michael@0 6143
michael@0 6144 if (NS_SUCCEEDED(rv)) {
michael@0 6145 rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr);
michael@0 6146 }
michael@0 6147
michael@0 6148 LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(), int(rv)));
michael@0 6149 }
michael@0 6150
michael@0 6151 void
michael@0 6152 nsHttpChannel::AsyncOnExamineCachedResponse()
michael@0 6153 {
michael@0 6154 gHttpHandler->OnExamineCachedResponse(this);
michael@0 6155
michael@0 6156 }
michael@0 6157
michael@0 6158 void
michael@0 6159 nsHttpChannel::UpdateAggregateCallbacks()
michael@0 6160 {
michael@0 6161 if (!mTransaction) {
michael@0 6162 return;
michael@0 6163 }
michael@0 6164 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 6165 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
michael@0 6166 NS_GetCurrentThread(),
michael@0 6167 getter_AddRefs(callbacks));
michael@0 6168 mTransaction->SetSecurityCallbacks(callbacks);
michael@0 6169 }
michael@0 6170
michael@0 6171 nsIPrincipal *
michael@0 6172 nsHttpChannel::GetPrincipal()
michael@0 6173 {
michael@0 6174 if (mPrincipal)
michael@0 6175 return mPrincipal;
michael@0 6176
michael@0 6177 nsIScriptSecurityManager *securityManager =
michael@0 6178 nsContentUtils::GetSecurityManager();
michael@0 6179
michael@0 6180 if (!securityManager)
michael@0 6181 return nullptr;
michael@0 6182
michael@0 6183 securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal));
michael@0 6184 if (!mPrincipal)
michael@0 6185 return nullptr;
michael@0 6186
michael@0 6187 // principals with unknown app ids do not work with the permission manager
michael@0 6188 if (mPrincipal->GetUnknownAppId())
michael@0 6189 mPrincipal = nullptr;
michael@0 6190
michael@0 6191 return mPrincipal;
michael@0 6192 }
michael@0 6193
michael@0 6194
michael@0 6195 NS_IMETHODIMP
michael@0 6196 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
michael@0 6197 {
michael@0 6198 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
michael@0 6199
michael@0 6200 nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
michael@0 6201 if (NS_SUCCEEDED(rv)) {
michael@0 6202 UpdateAggregateCallbacks();
michael@0 6203 }
michael@0 6204 return rv;
michael@0 6205 }
michael@0 6206
michael@0 6207 NS_IMETHODIMP
michael@0 6208 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
michael@0 6209 {
michael@0 6210 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
michael@0 6211
michael@0 6212 nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
michael@0 6213 if (NS_SUCCEEDED(rv)) {
michael@0 6214 UpdateAggregateCallbacks();
michael@0 6215 }
michael@0 6216 return rv;
michael@0 6217 }
michael@0 6218
michael@0 6219 nsPerformance*
michael@0 6220 nsHttpChannel::GetPerformance()
michael@0 6221 {
michael@0 6222 // If performance timing is disabled, there is no need for the nsPerformance
michael@0 6223 // object anymore.
michael@0 6224 if (!mTimingEnabled) {
michael@0 6225 return nullptr;
michael@0 6226 }
michael@0 6227 nsCOMPtr<nsILoadContext> loadContext;
michael@0 6228 NS_QueryNotificationCallbacks(this, loadContext);
michael@0 6229 if (!loadContext) {
michael@0 6230 return nullptr;
michael@0 6231 }
michael@0 6232 nsCOMPtr<nsIDOMWindow> domWindow;
michael@0 6233 loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
michael@0 6234 if (!domWindow) {
michael@0 6235 return nullptr;
michael@0 6236 }
michael@0 6237 nsCOMPtr<nsPIDOMWindow> pDomWindow = do_QueryInterface(domWindow);
michael@0 6238 if (!pDomWindow) {
michael@0 6239 return nullptr;
michael@0 6240 }
michael@0 6241 if (!pDomWindow->IsInnerWindow()) {
michael@0 6242 pDomWindow = pDomWindow->GetCurrentInnerWindow();
michael@0 6243 if (!pDomWindow) {
michael@0 6244 return nullptr;
michael@0 6245 }
michael@0 6246 }
michael@0 6247
michael@0 6248 nsPerformance* docPerformance = pDomWindow->GetPerformance();
michael@0 6249 if (!docPerformance) {
michael@0 6250 return nullptr;
michael@0 6251 }
michael@0 6252 // iframes should be added to the parent's entries list.
michael@0 6253 if (mLoadFlags & LOAD_DOCUMENT_URI) {
michael@0 6254 return docPerformance->GetParentPerformance();
michael@0 6255 }
michael@0 6256 return docPerformance;
michael@0 6257 }
michael@0 6258
michael@0 6259 void
michael@0 6260 nsHttpChannel::ForcePending(bool aForcePending)
michael@0 6261 {
michael@0 6262 // Set true here so IsPending will return true.
michael@0 6263 // Required for callback diversion from child back to parent. In such cases
michael@0 6264 // OnStopRequest can be called in the parent before callbacks are diverted
michael@0 6265 // back from the child to the listener in the parent.
michael@0 6266 mForcePending = aForcePending;
michael@0 6267 }
michael@0 6268
michael@0 6269 NS_IMETHODIMP
michael@0 6270 nsHttpChannel::IsPending(bool *aIsPending)
michael@0 6271 {
michael@0 6272 NS_ENSURE_ARG_POINTER(aIsPending);
michael@0 6273 *aIsPending = mIsPending || mForcePending;
michael@0 6274 return NS_OK;
michael@0 6275 }
michael@0 6276
michael@0 6277 } } // namespace mozilla::net

mercurial