Thu, 15 Jan 2015 21:03:48 +0100
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, ¤tAge); |
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 |