1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/Assertions.h"
8 #include "mozilla/LinkedList.h"
9
10 #include "nsCORSListenerProxy.h"
11 #include "nsIChannel.h"
12 #include "nsIHttpChannel.h"
13 #include "HttpChannelChild.h"
14 #include "nsIHttpChannelInternal.h"
15 #include "nsError.h"
16 #include "nsContentUtils.h"
17 #include "nsIScriptSecurityManager.h"
18 #include "nsNetUtil.h"
19 #include "nsIInterfaceRequestorUtils.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsMimeTypes.h"
22 #include "nsIStreamConverterService.h"
23 #include "nsStringStream.h"
24 #include "nsGkAtoms.h"
25 #include "nsWhitespaceTokenizer.h"
26 #include "nsIChannelEventSink.h"
27 #include "nsIAsyncVerifyRedirectCallback.h"
28 #include "nsCharSeparatedTokenizer.h"
29 #include "nsAsyncRedirectVerifyHelper.h"
30 #include "nsClassHashtable.h"
31 #include "nsHashKeys.h"
32 #include "nsStreamUtils.h"
33 #include "mozilla/Preferences.h"
34 #include "nsIScriptError.h"
35 #include "nsILoadGroup.h"
36 #include "nsILoadContext.h"
37 #include "nsIConsoleService.h"
38 #include "nsIDOMNode.h"
39 #include "nsIDOMWindowUtils.h"
40 #include "nsIDOMWindow.h"
41 #include "nsINetworkInterceptController.h"
42 #include "NullPrincipal.h"
43 #include "nsICorsPreflightCallback.h"
44 #include "nsISupportsImpl.h"
45 #include "nsHttpChannel.h"
46 #include "mozilla/LoadInfo.h"
47 #include "nsIHttpHeaderVisitor.h"
48 #include "nsQueryObject.h"
49 #include <algorithm>
50
51 using namespace mozilla;
52
53 #define PREFLIGHT_CACHE_SIZE 100
54
55 static bool gDisableCORS = false;
56 static bool gDisableCORSPrivateData = false;
57
LogBlockedRequest(nsIRequest * aRequest,const char * aProperty,const char16_t * aParam,nsIHttpChannel * aCreatingChannel)58 static void LogBlockedRequest(nsIRequest* aRequest, const char* aProperty,
59 const char16_t* aParam,
60 nsIHttpChannel* aCreatingChannel) {
61 nsresult rv = NS_OK;
62
63 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
64 nsCOMPtr<nsIURI> aUri;
65 channel->GetURI(getter_AddRefs(aUri));
66 nsAutoCString spec;
67 if (aUri) {
68 spec = aUri->GetSpecOrDefault();
69 }
70
71 // Generate the error message
72 nsAutoString blockedMessage;
73 NS_ConvertUTF8toUTF16 specUTF16(spec);
74 const char16_t* params[] = {specUTF16.get(), aParam};
75 rv = nsContentUtils::FormatLocalizedString(
76 nsContentUtils::eSECURITY_PROPERTIES, aProperty, params, blockedMessage);
77
78 if (NS_FAILED(rv)) {
79 NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
80 return;
81 }
82
83 nsAutoString msg(blockedMessage.get());
84
85 if (XRE_IsParentProcess()) {
86 if (aCreatingChannel) {
87 rv = aCreatingChannel->LogBlockedCORSRequest(msg);
88 if (NS_SUCCEEDED(rv)) {
89 return;
90 }
91 }
92 NS_WARNING(
93 "Failed to log blocked cross-site request to web console from "
94 "parent->child, falling back to browser console");
95 }
96
97 // log message ourselves
98 uint64_t innerWindowID = nsContentUtils::GetInnerWindowID(aRequest);
99 nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, msg);
100 }
101
102 //////////////////////////////////////////////////////////////////////////
103 // Preflight cache
104
105 class nsPreflightCache {
106 public:
107 struct TokenTime {
108 nsCString token;
109 TimeStamp expirationTime;
110 };
111
112 struct CacheEntry : public LinkedListElement<CacheEntry> {
CacheEntrynsPreflightCache::CacheEntry113 explicit CacheEntry(nsCString& aKey) : mKey(aKey) {
114 MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
115 }
116
~CacheEntrynsPreflightCache::CacheEntry117 ~CacheEntry() { MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); }
118
119 void PurgeExpired(TimeStamp now);
120 bool CheckRequest(const nsCString& aMethod,
121 const nsTArray<nsCString>& aCustomHeaders);
122
123 nsCString mKey;
124 nsTArray<TokenTime> mMethods;
125 nsTArray<TokenTime> mHeaders;
126 };
127
nsPreflightCache()128 nsPreflightCache() { MOZ_COUNT_CTOR(nsPreflightCache); }
129
~nsPreflightCache()130 ~nsPreflightCache() {
131 Clear();
132 MOZ_COUNT_DTOR(nsPreflightCache);
133 }
134
Initialize()135 bool Initialize() { return true; }
136
137 CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
138 bool aWithCredentials, bool aCreate);
139 void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
140
141 void Clear();
142
143 private:
144 static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
145 bool aWithCredentials, nsACString& _retval);
146
147 nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
148 LinkedList<CacheEntry> mList;
149 };
150
151 // Will be initialized in EnsurePreflightCache.
152 static nsPreflightCache* sPreflightCache = nullptr;
153
EnsurePreflightCache()154 static bool EnsurePreflightCache() {
155 if (sPreflightCache) return true;
156
157 nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache());
158
159 if (newCache->Initialize()) {
160 sPreflightCache = newCache.forget();
161 return true;
162 }
163
164 return false;
165 }
166
PurgeExpired(TimeStamp now)167 void nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now) {
168 for (uint32_t i = 0, len = mMethods.Length(); i < len; ++i) {
169 if (now >= mMethods[i].expirationTime) {
170 mMethods.UnorderedRemoveElementAt(i);
171 --i; // Examine the element again, if necessary.
172 --len;
173 }
174 }
175 for (uint32_t i = 0, len = mHeaders.Length(); i < len; ++i) {
176 if (now >= mHeaders[i].expirationTime) {
177 mHeaders.UnorderedRemoveElementAt(i);
178 --i; // Examine the element again, if necessary.
179 --len;
180 }
181 }
182 }
183
CheckRequest(const nsCString & aMethod,const nsTArray<nsCString> & aHeaders)184 bool nsPreflightCache::CacheEntry::CheckRequest(
185 const nsCString& aMethod, const nsTArray<nsCString>& aHeaders) {
186 PurgeExpired(TimeStamp::NowLoRes());
187
188 if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
189 struct CheckToken {
190 bool Equals(const TokenTime& e, const nsCString& method) const {
191 return e.token.Equals(method);
192 }
193 };
194
195 if (!mMethods.Contains(aMethod, CheckToken())) {
196 return false;
197 }
198 }
199
200 struct CheckHeaderToken {
201 bool Equals(const TokenTime& e, const nsCString& header) const {
202 return e.token.Equals(header, comparator);
203 }
204
205 const nsCaseInsensitiveCStringComparator comparator;
206 } checker;
207 for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
208 if (!mHeaders.Contains(aHeaders[i], checker)) {
209 return false;
210 }
211 }
212
213 return true;
214 }
215
GetEntry(nsIURI * aURI,nsIPrincipal * aPrincipal,bool aWithCredentials,bool aCreate)216 nsPreflightCache::CacheEntry* nsPreflightCache::GetEntry(
217 nsIURI* aURI, nsIPrincipal* aPrincipal, bool aWithCredentials,
218 bool aCreate) {
219 nsCString key;
220 if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
221 NS_WARNING("Invalid cache key!");
222 return nullptr;
223 }
224
225 CacheEntry* existingEntry = nullptr;
226
227 if (mTable.Get(key, &existingEntry)) {
228 // Entry already existed so just return it. Also update the LRU list.
229
230 // Move to the head of the list.
231 existingEntry->removeFrom(mList);
232 mList.insertFront(existingEntry);
233
234 return existingEntry;
235 }
236
237 if (!aCreate) {
238 return nullptr;
239 }
240
241 // This is a new entry, allocate and insert into the table now so that any
242 // failures don't cause items to be removed from a full cache.
243 CacheEntry* newEntry = new CacheEntry(key);
244 if (!newEntry) {
245 NS_WARNING("Failed to allocate new cache entry!");
246 return nullptr;
247 }
248
249 NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
250 "Something is borked, too many entries in the cache!");
251
252 // Now enforce the max count.
253 if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
254 // Try to kick out all the expired entries.
255 TimeStamp now = TimeStamp::NowLoRes();
256 for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
257 nsAutoPtr<CacheEntry>& entry = iter.Data();
258 entry->PurgeExpired(now);
259
260 if (entry->mHeaders.IsEmpty() && entry->mMethods.IsEmpty()) {
261 // Expired, remove from the list as well as the hash table.
262 entry->removeFrom(sPreflightCache->mList);
263 iter.Remove();
264 }
265 }
266
267 // If that didn't remove anything then kick out the least recently used
268 // entry.
269 if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
270 CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
271 MOZ_ASSERT(lruEntry);
272
273 // This will delete 'lruEntry'.
274 mTable.Remove(lruEntry->mKey);
275
276 NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
277 "Somehow tried to remove an entry that was never added!");
278 }
279 }
280
281 mTable.Put(key, newEntry);
282 mList.insertFront(newEntry);
283
284 return newEntry;
285 }
286
RemoveEntries(nsIURI * aURI,nsIPrincipal * aPrincipal)287 void nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal) {
288 CacheEntry* entry;
289 nsCString key;
290 if (GetCacheKey(aURI, aPrincipal, true, key) && mTable.Get(key, &entry)) {
291 entry->removeFrom(mList);
292 mTable.Remove(key);
293 }
294
295 if (GetCacheKey(aURI, aPrincipal, false, key) && mTable.Get(key, &entry)) {
296 entry->removeFrom(mList);
297 mTable.Remove(key);
298 }
299 }
300
Clear()301 void nsPreflightCache::Clear() {
302 mList.clear();
303 mTable.Clear();
304 }
305
GetCacheKey(nsIURI * aURI,nsIPrincipal * aPrincipal,bool aWithCredentials,nsACString & _retval)306 /* static */ bool nsPreflightCache::GetCacheKey(nsIURI* aURI,
307 nsIPrincipal* aPrincipal,
308 bool aWithCredentials,
309 nsACString& _retval) {
310 NS_ASSERTION(aURI, "Null uri!");
311 NS_ASSERTION(aPrincipal, "Null principal!");
312
313 NS_NAMED_LITERAL_CSTRING(space, " ");
314
315 nsCOMPtr<nsIURI> uri;
316 nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
317 NS_ENSURE_SUCCESS(rv, false);
318
319 nsAutoCString scheme, host, port;
320 if (uri) {
321 uri->GetScheme(scheme);
322 uri->GetHost(host);
323 port.AppendInt(NS_GetRealPort(uri));
324 }
325
326 if (aWithCredentials) {
327 _retval.AssignLiteral("cred");
328 } else {
329 _retval.AssignLiteral("nocred");
330 }
331
332 nsAutoCString spec;
333 rv = aURI->GetSpec(spec);
334 NS_ENSURE_SUCCESS(rv, false);
335
336 _retval.Append(space + scheme + space + host + space + port + space + spec);
337
338 return true;
339 }
340
341 //////////////////////////////////////////////////////////////////////////
342 // nsCORSListenerProxy
343
NS_IMPL_ISUPPORTS(nsCORSListenerProxy,nsIStreamListener,nsIRequestObserver,nsIChannelEventSink,nsIInterfaceRequestor,nsIThreadRetargetableStreamListener)344 NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, nsIRequestObserver,
345 nsIChannelEventSink, nsIInterfaceRequestor,
346 nsIThreadRetargetableStreamListener)
347
348 /* static */
349 void nsCORSListenerProxy::Startup() {
350 Preferences::AddBoolVarCache(&gDisableCORS, "content.cors.disable");
351 Preferences::AddBoolVarCache(&gDisableCORSPrivateData,
352 "content.cors.no_private_data");
353 }
354
355 /* static */
Shutdown()356 void nsCORSListenerProxy::Shutdown() {
357 delete sPreflightCache;
358 sPreflightCache = nullptr;
359 }
360
nsCORSListenerProxy(nsIStreamListener * aOuter,nsIPrincipal * aRequestingPrincipal,bool aWithCredentials)361 nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
362 nsIPrincipal* aRequestingPrincipal,
363 bool aWithCredentials)
364 : mOuterListener(aOuter),
365 mRequestingPrincipal(aRequestingPrincipal),
366 mOriginHeaderPrincipal(aRequestingPrincipal),
367 mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
368 mRequestApproved(false),
369 mHasBeenCrossSite(false),
370 mMutex("nsCORSListenerProxy") {}
371
~nsCORSListenerProxy()372 nsCORSListenerProxy::~nsCORSListenerProxy() {}
373
Init(nsIChannel * aChannel,DataURIHandling aAllowDataURI)374 nsresult nsCORSListenerProxy::Init(nsIChannel* aChannel,
375 DataURIHandling aAllowDataURI) {
376 aChannel->GetNotificationCallbacks(
377 getter_AddRefs(mOuterNotificationCallbacks));
378 aChannel->SetNotificationCallbacks(this);
379
380 nsresult rv = UpdateChannel(aChannel, aAllowDataURI, UpdateType::Default);
381 if (NS_FAILED(rv)) {
382 {
383 MutexAutoLock lock(mMutex);
384 mOuterListener = nullptr;
385 }
386 mRequestingPrincipal = nullptr;
387 mOriginHeaderPrincipal = nullptr;
388 mOuterNotificationCallbacks = nullptr;
389 mHttpChannel = nullptr;
390 }
391 #ifdef DEBUG
392 mInited = true;
393 #endif
394 return rv;
395 }
396
397 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)398 nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
399 nsISupports* aContext) {
400 MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
401 nsresult rv = CheckRequestApproved(aRequest);
402 mRequestApproved = NS_SUCCEEDED(rv);
403 if (!mRequestApproved) {
404 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
405 if (channel) {
406 nsCOMPtr<nsIURI> uri;
407 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
408 if (uri) {
409 if (sPreflightCache) {
410 // OK to use mRequestingPrincipal since preflights never get
411 // redirected.
412 sPreflightCache->RemoveEntries(uri, mRequestingPrincipal);
413 } else {
414 nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
415 do_QueryInterface(channel);
416 if (httpChannelChild) {
417 rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
418 uri, mRequestingPrincipal);
419 if (NS_FAILED(rv)) {
420 // Only warn here to ensure we fall through the request Cancel()
421 // and outer listener OnStartRequest() calls.
422 NS_WARNING("Failed to remove CORS preflight cache entry!");
423 }
424 }
425 }
426 }
427 }
428
429 aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
430 nsCOMPtr<nsIStreamListener> listener;
431 {
432 MutexAutoLock lock(mMutex);
433 listener = mOuterListener;
434 }
435 listener->OnStartRequest(aRequest, aContext);
436
437 return NS_ERROR_DOM_BAD_URI;
438 }
439
440 nsCOMPtr<nsIStreamListener> listener;
441 {
442 MutexAutoLock lock(mMutex);
443 listener = mOuterListener;
444 }
445 return listener->OnStartRequest(aRequest, aContext);
446 }
447
448 namespace {
449 class CheckOriginHeader final : public nsIHttpHeaderVisitor {
450 public:
451 NS_DECL_ISUPPORTS
452
CheckOriginHeader()453 CheckOriginHeader() : mHeaderCount(0) {}
454
455 NS_IMETHOD
VisitHeader(const nsACString & aHeader,const nsACString & aValue)456 VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
457 if (aHeader.EqualsLiteral("Access-Control-Allow-Origin")) {
458 mHeaderCount++;
459 }
460
461 if (mHeaderCount > 1) {
462 return NS_ERROR_DOM_BAD_URI;
463 }
464 return NS_OK;
465 }
466
467 private:
468 uint32_t mHeaderCount;
469
~CheckOriginHeader()470 ~CheckOriginHeader() {}
471 };
472
473 NS_IMPL_ISUPPORTS(CheckOriginHeader, nsIHttpHeaderVisitor)
474 } // namespace
475
CheckRequestApproved(nsIRequest * aRequest)476 nsresult nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest) {
477 // Check if this was actually a cross domain request
478 if (!mHasBeenCrossSite) {
479 return NS_OK;
480 }
481 nsCOMPtr<nsIHttpChannel> topChannel;
482 topChannel.swap(mHttpChannel);
483
484 if (gDisableCORS) {
485 LogBlockedRequest(aRequest, "CORSDisabled", nullptr, topChannel);
486 return NS_ERROR_DOM_BAD_URI;
487 }
488
489 // Check if the request failed
490 nsresult status;
491 nsresult rv = aRequest->GetStatus(&status);
492 if (NS_FAILED(rv)) {
493 return rv;
494 }
495
496 if (NS_FAILED(status)) {
497 return status;
498 }
499
500 // Test that things worked on a HTTP level
501 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
502 if (!http) {
503 LogBlockedRequest(aRequest, "CORSRequestNotHttp", nullptr, topChannel);
504 return NS_ERROR_DOM_BAD_URI;
505 }
506
507 nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
508 NS_ENSURE_STATE(internal);
509 bool responseSynthesized = false;
510 if (NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized)) &&
511 responseSynthesized) {
512 // For synthesized responses, we don't need to perform any checks.
513 // Note: This would be unsafe if we ever changed our behavior to allow
514 // service workers to intercept CORS preflights.
515 return NS_OK;
516 }
517
518 // Check the Access-Control-Allow-Origin header
519 RefPtr<CheckOriginHeader> visitor = new CheckOriginHeader();
520 nsAutoCString allowedOriginHeader;
521
522 // check for duplicate headers
523 rv = http->VisitOriginalResponseHeaders(visitor);
524 if (NS_FAILED(rv)) {
525 LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin", nullptr,
526 topChannel);
527 return rv;
528 }
529
530 rv = http->GetResponseHeader(
531 NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
532 if (NS_FAILED(rv)) {
533 LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr, topChannel);
534 return rv;
535 }
536
537 // Bug 1210985 - Explicitly point out the error that the credential is
538 // not supported if the allowing origin is '*'. Note that this check
539 // has to be done before the condition
540 //
541 // >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
542 //
543 // below since "if (A && B)" is included in "if (A || !B)".
544 //
545 if (mWithCredentials && allowedOriginHeader.EqualsLiteral("*")) {
546 LogBlockedRequest(aRequest, "CORSNotSupportingCredentials", nullptr,
547 topChannel);
548 return NS_ERROR_DOM_BAD_URI;
549 }
550
551 if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
552 MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
553 nsAutoCString origin;
554 nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
555
556 if (!allowedOriginHeader.Equals(origin)) {
557 LogBlockedRequest(aRequest, "CORSAllowOriginNotMatchingOrigin",
558 NS_ConvertUTF8toUTF16(allowedOriginHeader).get(),
559 topChannel);
560 return NS_ERROR_DOM_BAD_URI;
561 }
562 }
563
564 // Check Access-Control-Allow-Credentials header
565 if (mWithCredentials) {
566 nsAutoCString allowCredentialsHeader;
567 rv = http->GetResponseHeader(
568 NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"),
569 allowCredentialsHeader);
570
571 if (!allowCredentialsHeader.EqualsLiteral("true")) {
572 LogBlockedRequest(aRequest, "CORSMissingAllowCredentials", nullptr,
573 topChannel);
574 return NS_ERROR_DOM_BAD_URI;
575 }
576 }
577
578 return NS_OK;
579 }
580
581 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatusCode)582 nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
583 nsresult aStatusCode) {
584 MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
585 nsCOMPtr<nsIStreamListener> listener;
586 {
587 MutexAutoLock lock(mMutex);
588 listener = mOuterListener.forget();
589 }
590 nsresult rv = listener->OnStopRequest(aRequest, aContext, aStatusCode);
591 mOuterNotificationCallbacks = nullptr;
592 mHttpChannel = nullptr;
593 return rv;
594 }
595
596 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)597 nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
598 nsISupports* aContext,
599 nsIInputStream* aInputStream,
600 uint64_t aOffset, uint32_t aCount) {
601 // NB: This can be called on any thread! But we're guaranteed that it is
602 // called between OnStartRequest and OnStopRequest, so we don't need to worry
603 // about races.
604
605 MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly");
606 if (!mRequestApproved) {
607 return NS_ERROR_DOM_BAD_URI;
608 }
609 nsCOMPtr<nsIStreamListener> listener;
610 {
611 MutexAutoLock lock(mMutex);
612 listener = mOuterListener;
613 }
614 return listener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset,
615 aCount);
616 }
617
SetInterceptController(nsINetworkInterceptController * aInterceptController)618 void nsCORSListenerProxy::SetInterceptController(
619 nsINetworkInterceptController* aInterceptController) {
620 mInterceptController = aInterceptController;
621 }
622
623 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)624 nsCORSListenerProxy::GetInterface(const nsIID& aIID, void** aResult) {
625 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
626 *aResult = static_cast<nsIChannelEventSink*>(this);
627 NS_ADDREF_THIS();
628
629 return NS_OK;
630 }
631
632 if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
633 mInterceptController) {
634 nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
635 *aResult = copy.forget().take();
636
637 return NS_OK;
638 }
639
640 return mOuterNotificationCallbacks
641 ? mOuterNotificationCallbacks->GetInterface(aIID, aResult)
642 : NS_ERROR_NO_INTERFACE;
643 }
644
645 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * aCb)646 nsCORSListenerProxy::AsyncOnChannelRedirect(
647 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
648 nsIAsyncVerifyRedirectCallback* aCb) {
649 nsresult rv;
650 if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
651 NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
652 // Internal redirects still need to be updated in order to maintain
653 // the correct headers. We use DataURIHandling::Allow, since unallowed
654 // data URIs should have been blocked before we got to the internal
655 // redirect.
656 rv = UpdateChannel(aNewChannel, DataURIHandling::Allow,
657 UpdateType::InternalOrHSTSRedirect);
658 if (NS_FAILED(rv)) {
659 NS_WARNING(
660 "nsCORSListenerProxy::AsyncOnChannelRedirect: "
661 "internal redirect UpdateChannel() returned failure");
662 aOldChannel->Cancel(rv);
663 return rv;
664 }
665 } else {
666 // A real, external redirect. Perform CORS checking on new URL.
667 rv = CheckRequestApproved(aOldChannel);
668 if (NS_FAILED(rv)) {
669 nsCOMPtr<nsIURI> oldURI;
670 NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
671 if (oldURI) {
672 if (sPreflightCache) {
673 // OK to use mRequestingPrincipal since preflights never get
674 // redirected.
675 sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal);
676 } else {
677 nsCOMPtr<nsIHttpChannelChild> httpChannelChild =
678 do_QueryInterface(aOldChannel);
679 if (httpChannelChild) {
680 rv = httpChannelChild->RemoveCorsPreflightCacheEntry(
681 oldURI, mRequestingPrincipal);
682 if (NS_FAILED(rv)) {
683 // Only warn here to ensure we call the channel Cancel() below
684 NS_WARNING("Failed to remove CORS preflight cache entry!");
685 }
686 }
687 }
688 }
689 aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
690 return NS_ERROR_DOM_BAD_URI;
691 }
692
693 if (mHasBeenCrossSite) {
694 // Once we've been cross-site, cross-origin redirects reset our source
695 // origin. Note that we need to call GetChannelURIPrincipal() because
696 // we are looking for the principal that is actually being loaded and not
697 // the principal that initiated the load.
698 nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
699 nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
700 aOldChannel, getter_AddRefs(oldChannelPrincipal));
701 nsCOMPtr<nsIPrincipal> newChannelPrincipal;
702 nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
703 aNewChannel, getter_AddRefs(newChannelPrincipal));
704 if (!oldChannelPrincipal || !newChannelPrincipal) {
705 rv = NS_ERROR_OUT_OF_MEMORY;
706 }
707
708 if (NS_SUCCEEDED(rv)) {
709 bool equal;
710 rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal);
711 if (NS_SUCCEEDED(rv) && !equal) {
712 // Spec says to set our source origin to a unique origin.
713 mOriginHeaderPrincipal =
714 NullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
715 }
716 }
717
718 if (NS_FAILED(rv)) {
719 aOldChannel->Cancel(rv);
720 return rv;
721 }
722 }
723
724 rv = UpdateChannel(aNewChannel, DataURIHandling::Disallow,
725 UpdateType::Default);
726 if (NS_FAILED(rv)) {
727 NS_WARNING(
728 "nsCORSListenerProxy::AsyncOnChannelRedirect: "
729 "UpdateChannel() returned failure");
730 aOldChannel->Cancel(rv);
731 return rv;
732 }
733 }
734
735 nsCOMPtr<nsIChannelEventSink> outer =
736 do_GetInterface(mOuterNotificationCallbacks);
737 if (outer) {
738 return outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCb);
739 }
740
741 aCb->OnRedirectVerifyCallback(NS_OK);
742
743 return NS_OK;
744 }
745
746 NS_IMETHODIMP
CheckListenerChain()747 nsCORSListenerProxy::CheckListenerChain() {
748 MOZ_ASSERT(NS_IsMainThread());
749
750 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener;
751 {
752 MutexAutoLock lock(mMutex);
753 retargetableListener = do_QueryInterface(mOuterListener);
754 }
755 if (!retargetableListener) {
756 return NS_ERROR_NO_INTERFACE;
757 }
758
759 return retargetableListener->CheckListenerChain();
760 }
761
762 // Please note that the CSP directive 'upgrade-insecure-requests' relies
763 // on the promise that channels get updated from http: to https: before
764 // the channel fetches any data from the netwerk. Such channels should
765 // not be blocked by CORS and marked as cross origin requests. E.g.:
766 // toplevel page: https://www.example.com loads
767 // xhr: http://www.example.com/foo which gets updated to
768 // https://www.example.com/foo
769 // In such a case we should bail out of CORS and rely on the promise that
770 // nsHttpChannel::Connect() upgrades the request from http to https.
CheckUpgradeInsecureRequestsPreventsCORS(nsIPrincipal * aRequestingPrincipal,nsIChannel * aChannel)771 bool CheckUpgradeInsecureRequestsPreventsCORS(
772 nsIPrincipal* aRequestingPrincipal, nsIChannel* aChannel) {
773 nsCOMPtr<nsIURI> channelURI;
774 nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
775 NS_ENSURE_SUCCESS(rv, false);
776 bool isHttpScheme = false;
777 rv = channelURI->SchemeIs("http", &isHttpScheme);
778 NS_ENSURE_SUCCESS(rv, false);
779
780 // upgrade insecure requests is only applicable to http requests
781 if (!isHttpScheme) {
782 return false;
783 }
784
785 nsCOMPtr<nsIURI> principalURI;
786 rv = aRequestingPrincipal->GetURI(getter_AddRefs(principalURI));
787 NS_ENSURE_SUCCESS(rv, false);
788
789 // if the requestingPrincipal does not have a uri, there is nothing to do
790 if (!principalURI) {
791 return false;
792 }
793
794 nsCOMPtr<nsIURI> originalURI;
795 rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
796 NS_ENSURE_SUCCESS(rv, false);
797
798 nsAutoCString principalHost, channelHost, origChannelHost;
799
800 // if we can not query a host from the uri, there is nothing to do
801 if (NS_FAILED(principalURI->GetAsciiHost(principalHost)) ||
802 NS_FAILED(channelURI->GetAsciiHost(channelHost)) ||
803 NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) {
804 return false;
805 }
806
807 // if the hosts do not match, there is nothing to do
808 if (!principalHost.EqualsIgnoreCase(channelHost.get())) {
809 return false;
810 }
811
812 // also check that uri matches the one of the originalURI
813 if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
814 return false;
815 }
816
817 nsCOMPtr<nsILoadInfo> loadInfo;
818 rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
819 NS_ENSURE_SUCCESS(rv, false);
820
821 if (!loadInfo) {
822 return false;
823 }
824
825 // lets see if the loadInfo indicates that the request will
826 // be upgraded before fetching any data from the netwerk.
827 return loadInfo->GetUpgradeInsecureRequests() ||
828 loadInfo->GetBrowserUpgradeInsecureRequests();
829 }
830
UpdateChannel(nsIChannel * aChannel,DataURIHandling aAllowDataURI,UpdateType aUpdateType)831 nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
832 DataURIHandling aAllowDataURI,
833 UpdateType aUpdateType) {
834 nsCOMPtr<nsIURI> uri, originalURI;
835 nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
836 NS_ENSURE_SUCCESS(rv, rv);
837 rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
838 NS_ENSURE_SUCCESS(rv, rv);
839
840 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
841
842 // exempt data URIs from the same origin check.
843 if (aAllowDataURI == DataURIHandling::Allow && originalURI == uri) {
844 bool dataScheme = false;
845 rv = uri->SchemeIs("data", &dataScheme);
846 NS_ENSURE_SUCCESS(rv, rv);
847 if (dataScheme) {
848 return NS_OK;
849 }
850 if (loadInfo && loadInfo->GetAboutBlankInherits() && NS_IsAboutBlank(uri)) {
851 return NS_OK;
852 }
853 }
854
855 // Set CORS attributes on channel so that intercepted requests get correct
856 // values. We have to do this here because the CheckMayLoad checks may lead
857 // to early return. We can't be sure this is an http channel though, so we
858 // can't return early on failure.
859 nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
860 if (internal) {
861 rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS);
862 NS_ENSURE_SUCCESS(rv, rv);
863 rv = internal->SetCorsIncludeCredentials(mWithCredentials);
864 NS_ENSURE_SUCCESS(rv, rv);
865 }
866
867 // TODO: Bug 1353683
868 // consider calling SetBlockedRequest in nsCORSListenerProxy::UpdateChannel
869 //
870 // Check that the uri is ok to load
871 rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
872 mRequestingPrincipal, uri, nsIScriptSecurityManager::STANDARD);
873 NS_ENSURE_SUCCESS(rv, rv);
874
875 if (originalURI != uri) {
876 rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
877 mRequestingPrincipal, originalURI, nsIScriptSecurityManager::STANDARD);
878 NS_ENSURE_SUCCESS(rv, rv);
879 }
880
881 if (!mHasBeenCrossSite &&
882 NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) &&
883 (originalURI == uri || NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(
884 originalURI, false, false)))) {
885 return NS_OK;
886 }
887
888 // if the CSP directive 'upgrade-insecure-requests' is used then we should
889 // not incorrectly require CORS if the only difference of a subresource
890 // request and the main page is the scheme.
891 // e.g. toplevel page: https://www.example.com loads
892 // xhr: http://www.example.com/somefoo,
893 // then the xhr request will be upgraded to https before it fetches any data
894 // from the netwerk, hence we shouldn't require CORS in that specific case.
895 if (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal,
896 aChannel)) {
897 return NS_OK;
898 }
899
900 // Check if we need to do a preflight, and if so set one up. This must be
901 // called once we know that the request is going, or has gone, cross-origin.
902 rv = CheckPreflightNeeded(aChannel, aUpdateType);
903 NS_ENSURE_SUCCESS(rv, rv);
904
905 // It's a cross site load
906 mHasBeenCrossSite = true;
907
908 nsCString userpass;
909 uri->GetUserPass(userpass);
910 NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
911
912 // If we have an expanded principal here, we'll reject the CORS request,
913 // because we can't send a useful Origin header which is required for CORS.
914 if (nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal)) {
915 return NS_ERROR_DOM_BAD_URI;
916 }
917
918 // Add the Origin header
919 nsAutoCString origin;
920 rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
921 NS_ENSURE_SUCCESS(rv, rv);
922
923 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
924 NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
925
926 rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false);
927 NS_ENSURE_SUCCESS(rv, rv);
928
929 // Make cookie-less if needed. We don't need to do anything here if the
930 // channel was opened with AsyncOpen2, since then AsyncOpen2 will take
931 // care of the cookie policy for us.
932 if (!mWithCredentials && (!loadInfo || !loadInfo->GetEnforceSecurity())) {
933 nsLoadFlags flags;
934 rv = http->GetLoadFlags(&flags);
935 NS_ENSURE_SUCCESS(rv, rv);
936
937 flags |= nsIRequest::LOAD_ANONYMOUS;
938 rv = http->SetLoadFlags(flags);
939 NS_ENSURE_SUCCESS(rv, rv);
940 }
941
942 mHttpChannel = http;
943
944 return NS_OK;
945 }
946
CheckPreflightNeeded(nsIChannel * aChannel,UpdateType aUpdateType)947 nsresult nsCORSListenerProxy::CheckPreflightNeeded(nsIChannel* aChannel,
948 UpdateType aUpdateType) {
949 // If this caller isn't using AsyncOpen2, or if this *is* a preflight channel,
950 // then we shouldn't initiate preflight for this channel.
951 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
952 if (!loadInfo ||
953 loadInfo->GetSecurityMode() !=
954 nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS ||
955 loadInfo->GetIsPreflight()) {
956 return NS_OK;
957 }
958
959 bool doPreflight = loadInfo->GetForcePreflight();
960
961 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
962 NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI);
963 nsAutoCString method;
964 Unused << http->GetRequestMethod(method);
965 if (!method.LowerCaseEqualsLiteral("get") &&
966 !method.LowerCaseEqualsLiteral("post") &&
967 !method.LowerCaseEqualsLiteral("head")) {
968 doPreflight = true;
969 }
970
971 // Avoid copying the array here
972 const nsTArray<nsCString>& loadInfoHeaders = loadInfo->CorsUnsafeHeaders();
973 if (!loadInfoHeaders.IsEmpty()) {
974 doPreflight = true;
975 }
976
977 // Add Content-Type header if needed
978 nsTArray<nsCString> headers;
979 nsAutoCString contentTypeHeader;
980 nsresult rv = http->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
981 contentTypeHeader);
982 // GetRequestHeader return an error if the header is not set. Don't add
983 // "content-type" to the list if that's the case.
984 if (NS_SUCCEEDED(rv) &&
985 !nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader) &&
986 !loadInfoHeaders.Contains(NS_LITERAL_CSTRING("content-type"),
987 nsCaseInsensitiveCStringArrayComparator())) {
988 headers.AppendElements(loadInfoHeaders);
989 headers.AppendElement(NS_LITERAL_CSTRING("content-type"));
990 doPreflight = true;
991 }
992
993 if (!doPreflight) {
994 return NS_OK;
995 }
996
997 // A preflight is needed. But if we've already been cross-site, then
998 // we already did a preflight when that happened, and so we're not allowed
999 // to do another preflight again.
1000 if (aUpdateType != UpdateType::InternalOrHSTSRedirect) {
1001 NS_ENSURE_FALSE(mHasBeenCrossSite, NS_ERROR_DOM_BAD_URI);
1002 }
1003
1004 nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(http);
1005 NS_ENSURE_TRUE(internal, NS_ERROR_DOM_BAD_URI);
1006
1007 internal->SetCorsPreflightParameters(headers.IsEmpty() ? loadInfoHeaders
1008 : headers);
1009
1010 return NS_OK;
1011 }
1012
1013 //////////////////////////////////////////////////////////////////////////
1014 // Preflight proxy
1015
1016 // Class used as streamlistener and notification callback when
1017 // doing the initial OPTIONS request for a CORS check
1018 class nsCORSPreflightListener final : public nsIStreamListener,
1019 public nsIInterfaceRequestor,
1020 public nsIChannelEventSink {
1021 public:
nsCORSPreflightListener(nsIPrincipal * aReferrerPrincipal,nsICorsPreflightCallback * aCallback,nsILoadContext * aLoadContext,bool aWithCredentials,const nsCString & aPreflightMethod,const nsTArray<nsCString> & aPreflightHeaders)1022 nsCORSPreflightListener(nsIPrincipal* aReferrerPrincipal,
1023 nsICorsPreflightCallback* aCallback,
1024 nsILoadContext* aLoadContext, bool aWithCredentials,
1025 const nsCString& aPreflightMethod,
1026 const nsTArray<nsCString>& aPreflightHeaders)
1027 : mPreflightMethod(aPreflightMethod),
1028 mPreflightHeaders(aPreflightHeaders),
1029 mReferrerPrincipal(aReferrerPrincipal),
1030 mCallback(aCallback),
1031 mLoadContext(aLoadContext),
1032 mWithCredentials(aWithCredentials) {}
1033
1034 NS_DECL_ISUPPORTS
1035 NS_DECL_NSISTREAMLISTENER
1036 NS_DECL_NSIREQUESTOBSERVER
1037 NS_DECL_NSIINTERFACEREQUESTOR
1038 NS_DECL_NSICHANNELEVENTSINK
1039
1040 nsresult CheckPreflightRequestApproved(nsIRequest* aRequest);
1041
1042 private:
~nsCORSPreflightListener()1043 ~nsCORSPreflightListener() {}
1044
1045 void AddResultToCache(nsIRequest* aRequest);
1046
1047 nsCString mPreflightMethod;
1048 nsTArray<nsCString> mPreflightHeaders;
1049 nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
1050 nsCOMPtr<nsICorsPreflightCallback> mCallback;
1051 nsCOMPtr<nsILoadContext> mLoadContext;
1052 bool mWithCredentials;
1053 };
1054
NS_IMPL_ISUPPORTS(nsCORSPreflightListener,nsIStreamListener,nsIRequestObserver,nsIInterfaceRequestor,nsIChannelEventSink)1055 NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
1056 nsIRequestObserver, nsIInterfaceRequestor,
1057 nsIChannelEventSink)
1058
1059 void nsCORSPreflightListener::AddResultToCache(nsIRequest* aRequest) {
1060 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
1061 NS_ASSERTION(http, "Request was not http");
1062
1063 // The "Access-Control-Max-Age" header should return an age in seconds.
1064 nsAutoCString headerVal;
1065 Unused << http->GetResponseHeader(
1066 NS_LITERAL_CSTRING("Access-Control-Max-Age"), headerVal);
1067 if (headerVal.IsEmpty()) {
1068 return;
1069 }
1070
1071 // Sanitize the string. We only allow 'delta-seconds' as specified by
1072 // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
1073 // trailing non-whitespace characters).
1074 uint32_t age = 0;
1075 nsACString::const_char_iterator iter, end;
1076 headerVal.BeginReading(iter);
1077 headerVal.EndReading(end);
1078 while (iter != end) {
1079 if (*iter < '0' || *iter > '9') {
1080 return;
1081 }
1082 age = age * 10 + (*iter - '0');
1083 // Cap at 24 hours. This also avoids overflow
1084 age = std::min(age, 86400U);
1085 ++iter;
1086 }
1087
1088 if (!age || !EnsurePreflightCache()) {
1089 return;
1090 }
1091
1092 // String seems fine, go ahead and cache.
1093 // Note that we have already checked that these headers follow the correct
1094 // syntax.
1095
1096 nsCOMPtr<nsIURI> uri;
1097 NS_GetFinalChannelURI(http, getter_AddRefs(uri));
1098
1099 TimeStamp expirationTime =
1100 TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
1101
1102 nsPreflightCache::CacheEntry* entry = sPreflightCache->GetEntry(
1103 uri, mReferrerPrincipal, mWithCredentials, true);
1104 if (!entry) {
1105 return;
1106 }
1107
1108 // The "Access-Control-Allow-Methods" header contains a comma separated
1109 // list of method names.
1110 Unused << http->GetResponseHeader(
1111 NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), headerVal);
1112
1113 nsCCharSeparatedTokenizer methods(headerVal, ',');
1114 while (methods.hasMoreTokens()) {
1115 const nsDependentCSubstring& method = methods.nextToken();
1116 if (method.IsEmpty()) {
1117 continue;
1118 }
1119 uint32_t i;
1120 for (i = 0; i < entry->mMethods.Length(); ++i) {
1121 if (entry->mMethods[i].token.Equals(method)) {
1122 entry->mMethods[i].expirationTime = expirationTime;
1123 break;
1124 }
1125 }
1126 if (i == entry->mMethods.Length()) {
1127 nsPreflightCache::TokenTime* newMethod = entry->mMethods.AppendElement();
1128 if (!newMethod) {
1129 return;
1130 }
1131
1132 newMethod->token = method;
1133 newMethod->expirationTime = expirationTime;
1134 }
1135 }
1136
1137 // The "Access-Control-Allow-Headers" header contains a comma separated
1138 // list of method names.
1139 Unused << http->GetResponseHeader(
1140 NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), headerVal);
1141
1142 nsCCharSeparatedTokenizer headers(headerVal, ',');
1143 while (headers.hasMoreTokens()) {
1144 const nsDependentCSubstring& header = headers.nextToken();
1145 if (header.IsEmpty()) {
1146 continue;
1147 }
1148 uint32_t i;
1149 for (i = 0; i < entry->mHeaders.Length(); ++i) {
1150 if (entry->mHeaders[i].token.Equals(header)) {
1151 entry->mHeaders[i].expirationTime = expirationTime;
1152 break;
1153 }
1154 }
1155 if (i == entry->mHeaders.Length()) {
1156 nsPreflightCache::TokenTime* newHeader = entry->mHeaders.AppendElement();
1157 if (!newHeader) {
1158 return;
1159 }
1160
1161 newHeader->token = header;
1162 newHeader->expirationTime = expirationTime;
1163 }
1164 }
1165 }
1166
1167 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)1168 nsCORSPreflightListener::OnStartRequest(nsIRequest* aRequest,
1169 nsISupports* aContext) {
1170 #ifdef DEBUG
1171 {
1172 nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
1173 bool responseSynthesized = false;
1174 if (internal &&
1175 NS_SUCCEEDED(internal->GetResponseSynthesized(&responseSynthesized))) {
1176 // For synthesized responses, we don't need to perform any checks.
1177 // This would be unsafe if we ever changed our behavior to allow
1178 // service workers to intercept CORS preflights.
1179 MOZ_ASSERT(!responseSynthesized);
1180 }
1181 }
1182 #endif
1183
1184 nsresult rv = CheckPreflightRequestApproved(aRequest);
1185
1186 if (NS_SUCCEEDED(rv)) {
1187 // Everything worked, try to cache and then fire off the actual request.
1188 AddResultToCache(aRequest);
1189
1190 mCallback->OnPreflightSucceeded();
1191 } else {
1192 mCallback->OnPreflightFailed(rv);
1193 }
1194
1195 return rv;
1196 }
1197
1198 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus)1199 nsCORSPreflightListener::OnStopRequest(nsIRequest* aRequest,
1200 nsISupports* aContext,
1201 nsresult aStatus) {
1202 mCallback = nullptr;
1203 return NS_OK;
1204 }
1205
1206 /** nsIStreamListener methods **/
1207
1208 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * ctxt,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)1209 nsCORSPreflightListener::OnDataAvailable(nsIRequest* aRequest,
1210 nsISupports* ctxt,
1211 nsIInputStream* inStr,
1212 uint64_t sourceOffset,
1213 uint32_t count) {
1214 uint32_t totalRead;
1215 return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
1216 }
1217
1218 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * callback)1219 nsCORSPreflightListener::AsyncOnChannelRedirect(
1220 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1221 nsIAsyncVerifyRedirectCallback* callback) {
1222 // Only internal redirects allowed for now.
1223 if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) &&
1224 !NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags))
1225 return NS_ERROR_DOM_BAD_URI;
1226
1227 callback->OnRedirectVerifyCallback(NS_OK);
1228 return NS_OK;
1229 }
1230
CheckPreflightRequestApproved(nsIRequest * aRequest)1231 nsresult nsCORSPreflightListener::CheckPreflightRequestApproved(
1232 nsIRequest* aRequest) {
1233 nsresult status;
1234 nsresult rv = aRequest->GetStatus(&status);
1235 NS_ENSURE_SUCCESS(rv, rv);
1236 NS_ENSURE_SUCCESS(status, status);
1237
1238 // Test that things worked on a HTTP level
1239 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
1240 nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
1241 NS_ENSURE_STATE(internal);
1242 nsCOMPtr<nsIHttpChannel> parentHttpChannel = do_QueryInterface(mCallback);
1243
1244 bool succeedded;
1245 rv = http->GetRequestSucceeded(&succeedded);
1246 if (NS_FAILED(rv) || !succeedded) {
1247 LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed", nullptr,
1248 parentHttpChannel);
1249 return NS_ERROR_DOM_BAD_URI;
1250 }
1251
1252 nsAutoCString headerVal;
1253 // The "Access-Control-Allow-Methods" header contains a comma separated
1254 // list of method names.
1255 Unused << http->GetResponseHeader(
1256 NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), headerVal);
1257 bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
1258 mPreflightMethod.EqualsLiteral("HEAD") ||
1259 mPreflightMethod.EqualsLiteral("POST");
1260 nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
1261 while (methodTokens.hasMoreTokens()) {
1262 const nsDependentCSubstring& method = methodTokens.nextToken();
1263 if (method.IsEmpty()) {
1264 continue;
1265 }
1266 if (!NS_IsValidHTTPToken(method)) {
1267 LogBlockedRequest(aRequest, "CORSInvalidAllowMethod",
1268 NS_ConvertUTF8toUTF16(method).get(), parentHttpChannel);
1269 return NS_ERROR_DOM_BAD_URI;
1270 }
1271 foundMethod |= mPreflightMethod.Equals(method);
1272 }
1273 if (!foundMethod) {
1274 LogBlockedRequest(aRequest, "CORSMethodNotFound", nullptr,
1275 parentHttpChannel);
1276 return NS_ERROR_DOM_BAD_URI;
1277 }
1278
1279 // The "Access-Control-Allow-Headers" header contains a comma separated
1280 // list of header names.
1281 Unused << http->GetResponseHeader(
1282 NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), headerVal);
1283 nsTArray<nsCString> headers;
1284 nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
1285 while (headerTokens.hasMoreTokens()) {
1286 const nsDependentCSubstring& header = headerTokens.nextToken();
1287 if (header.IsEmpty()) {
1288 continue;
1289 }
1290 if (!NS_IsValidHTTPToken(header)) {
1291 LogBlockedRequest(aRequest, "CORSInvalidAllowHeader",
1292 NS_ConvertUTF8toUTF16(header).get(), parentHttpChannel);
1293 return NS_ERROR_DOM_BAD_URI;
1294 }
1295 headers.AppendElement(header);
1296 }
1297 for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
1298 const auto& comparator = nsCaseInsensitiveCStringArrayComparator();
1299 if (!headers.Contains(mPreflightHeaders[i], comparator)) {
1300 LogBlockedRequest(aRequest, "CORSMissingAllowHeaderFromPreflight",
1301 NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(),
1302 parentHttpChannel);
1303 return NS_ERROR_DOM_BAD_URI;
1304 }
1305 }
1306
1307 return NS_OK;
1308 }
1309
1310 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)1311 nsCORSPreflightListener::GetInterface(const nsIID& aIID, void** aResult) {
1312 if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
1313 nsCOMPtr<nsILoadContext> copy = mLoadContext;
1314 copy.forget(aResult);
1315 return NS_OK;
1316 }
1317
1318 return QueryInterface(aIID, aResult);
1319 }
1320
RemoveFromCorsPreflightCache(nsIURI * aURI,nsIPrincipal * aRequestingPrincipal)1321 void nsCORSListenerProxy::RemoveFromCorsPreflightCache(
1322 nsIURI* aURI, nsIPrincipal* aRequestingPrincipal) {
1323 MOZ_ASSERT(XRE_IsParentProcess());
1324 if (sPreflightCache) {
1325 sPreflightCache->RemoveEntries(aURI, aRequestingPrincipal);
1326 }
1327 }
1328
1329 // static
StartCORSPreflight(nsIChannel * aRequestChannel,nsICorsPreflightCallback * aCallback,nsTArray<nsCString> & aUnsafeHeaders,nsIChannel ** aPreflightChannel)1330 nsresult nsCORSListenerProxy::StartCORSPreflight(
1331 nsIChannel* aRequestChannel, nsICorsPreflightCallback* aCallback,
1332 nsTArray<nsCString>& aUnsafeHeaders, nsIChannel** aPreflightChannel) {
1333 *aPreflightChannel = nullptr;
1334
1335 if (gDisableCORS) {
1336 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequestChannel);
1337 LogBlockedRequest(aRequestChannel, "CORSDisabled", nullptr, http);
1338 return NS_ERROR_DOM_BAD_URI;
1339 }
1340
1341 nsAutoCString method;
1342 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
1343 NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
1344 Unused << httpChannel->GetRequestMethod(method);
1345
1346 nsCOMPtr<nsIURI> uri;
1347 nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
1348 NS_ENSURE_SUCCESS(rv, rv);
1349
1350 nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
1351 MOZ_ASSERT(originalLoadInfo,
1352 "can not perform CORS preflight without a loadInfo");
1353 if (!originalLoadInfo) {
1354 return NS_ERROR_FAILURE;
1355 }
1356
1357 MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
1358 nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
1359 "how did we end up here?");
1360
1361 nsCOMPtr<nsIPrincipal> principal = originalLoadInfo->LoadingPrincipal();
1362 MOZ_ASSERT(principal && originalLoadInfo->GetExternalContentPolicyType() !=
1363 nsIContentPolicy::TYPE_DOCUMENT,
1364 "Should not do CORS loads for top-level loads, so a "
1365 "loadingPrincipal should always exist.");
1366 bool withCredentials =
1367 originalLoadInfo->GetCookiePolicy() == nsILoadInfo::SEC_COOKIES_INCLUDE;
1368
1369 nsPreflightCache::CacheEntry* entry =
1370 sPreflightCache
1371 ? sPreflightCache->GetEntry(uri, principal, withCredentials, false)
1372 : nullptr;
1373
1374 if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
1375 aCallback->OnPreflightSucceeded();
1376 return NS_OK;
1377 }
1378
1379 // Either it wasn't cached or the cached result has expired. Build a
1380 // channel for the OPTIONS request.
1381
1382 nsCOMPtr<nsILoadInfo> loadInfo =
1383 static_cast<mozilla::LoadInfo*>(originalLoadInfo.get())
1384 ->CloneForNewRequest();
1385 static_cast<mozilla::LoadInfo*>(loadInfo.get())->SetIsPreflight();
1386
1387 nsCOMPtr<nsILoadGroup> loadGroup;
1388 rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1389 NS_ENSURE_SUCCESS(rv, rv);
1390
1391 // We want to give the preflight channel's notification callbacks the same
1392 // load context as the original channel's notification callbacks had. We
1393 // don't worry about a load context provided via the loadgroup here, since
1394 // they have the same loadgroup.
1395 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1396 rv = aRequestChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
1397 NS_ENSURE_SUCCESS(rv, rv);
1398 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
1399
1400 nsLoadFlags loadFlags;
1401 rv = aRequestChannel->GetLoadFlags(&loadFlags);
1402 NS_ENSURE_SUCCESS(rv, rv);
1403
1404 // Preflight requests should never be intercepted by service workers and
1405 // are always anonymous.
1406 // NOTE: We ignore CORS checks on synthesized responses (see the CORS
1407 // preflights, then we need to extend the GetResponseSynthesized() check in
1408 // nsCORSListenerProxy::CheckRequestApproved()). If we change our behavior
1409 // here and allow service workers to intercept CORS preflights, then that
1410 // check won't be safe any more.
1411 loadFlags |=
1412 nsIChannel::LOAD_BYPASS_SERVICE_WORKER | nsIRequest::LOAD_ANONYMOUS;
1413
1414 nsCOMPtr<nsIChannel> preflightChannel;
1415 rv = NS_NewChannelInternal(getter_AddRefs(preflightChannel), uri, loadInfo,
1416 nullptr, // PerformanceStorage
1417 loadGroup,
1418 nullptr, // aCallbacks
1419 loadFlags);
1420 NS_ENSURE_SUCCESS(rv, rv);
1421
1422 // Set method and headers
1423 nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
1424 NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
1425
1426 rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
1427 NS_ENSURE_SUCCESS(rv, rv);
1428
1429 rv = preHttp->SetRequestHeader(
1430 NS_LITERAL_CSTRING("Access-Control-Request-Method"), method, false);
1431 NS_ENSURE_SUCCESS(rv, rv);
1432
1433 // Set the CORS preflight channel's warning reporter to be the same as the
1434 // requesting channel so that all log messages are able to be reported through
1435 // the warning reporter.
1436 RefPtr<nsHttpChannel> reqCh = do_QueryObject(aRequestChannel);
1437 RefPtr<nsHttpChannel> preCh = do_QueryObject(preHttp);
1438 if (preCh && reqCh) { // there are other implementers of nsIHttpChannel
1439 preCh->SetWarningReporter(reqCh->GetWarningReporter());
1440 }
1441
1442 nsTArray<nsCString> preflightHeaders;
1443 if (!aUnsafeHeaders.IsEmpty()) {
1444 for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) {
1445 preflightHeaders.AppendElement();
1446 ToLowerCase(aUnsafeHeaders[i], preflightHeaders[i]);
1447 }
1448 preflightHeaders.Sort();
1449 nsAutoCString headers;
1450 for (uint32_t i = 0; i < preflightHeaders.Length(); ++i) {
1451 if (i != 0) {
1452 headers += ',';
1453 }
1454 headers += preflightHeaders[i];
1455 }
1456 rv = preHttp->SetRequestHeader(
1457 NS_LITERAL_CSTRING("Access-Control-Request-Headers"), headers, false);
1458 NS_ENSURE_SUCCESS(rv, rv);
1459 }
1460
1461 // Set up listener which will start the original channel
1462 RefPtr<nsCORSPreflightListener> preflightListener =
1463 new nsCORSPreflightListener(principal, aCallback, loadContext,
1464 withCredentials, method, preflightHeaders);
1465
1466 rv = preflightChannel->SetNotificationCallbacks(preflightListener);
1467 NS_ENSURE_SUCCESS(rv, rv);
1468
1469 // Start preflight
1470 rv = preflightChannel->AsyncOpen2(preflightListener);
1471 NS_ENSURE_SUCCESS(rv, rv);
1472
1473 // Return newly created preflight channel
1474 preflightChannel.forget(aPreflightChannel);
1475
1476 return NS_OK;
1477 }
1478
1479 // static
LogBlockedCORSRequest(uint64_t aInnerWindowID,const nsAString & aMessage)1480 void nsCORSListenerProxy::LogBlockedCORSRequest(uint64_t aInnerWindowID,
1481 const nsAString& aMessage) {
1482 nsresult rv = NS_OK;
1483
1484 // Build the error object and log it to the console
1485 nsCOMPtr<nsIConsoleService> console(
1486 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1487 if (NS_FAILED(rv)) {
1488 NS_WARNING("Failed to log blocked cross-site request (no console)");
1489 return;
1490 }
1491
1492 nsCOMPtr<nsIScriptError> scriptError =
1493 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
1494 if (NS_FAILED(rv)) {
1495 NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
1496 return;
1497 }
1498
1499 // query innerWindowID and log to web console, otherwise log to
1500 // the error to the browser console.
1501 if (aInnerWindowID > 0) {
1502 rv = scriptError->InitWithSanitizedSource(aMessage,
1503 EmptyString(), // sourceName
1504 EmptyString(), // sourceLine
1505 0, // lineNumber
1506 0, // columnNumber
1507 nsIScriptError::warningFlag,
1508 "CORS", aInnerWindowID);
1509 } else {
1510 rv = scriptError->Init(aMessage,
1511 EmptyString(), // sourceName
1512 EmptyString(), // sourceLine
1513 0, // lineNumber
1514 0, // columnNumber
1515 nsIScriptError::warningFlag, "CORS");
1516 }
1517 if (NS_FAILED(rv)) {
1518 NS_WARNING(
1519 "Failed to log blocked cross-site request (scriptError init failed)");
1520 return;
1521 }
1522 console->LogMessage(scriptError);
1523 }
1524