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