content/base/src/nsCrossSiteListenerProxy.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/Assertions.h"
     7 #include "mozilla/LinkedList.h"
     9 #include "nsCrossSiteListenerProxy.h"
    10 #include "nsIChannel.h"
    11 #include "nsIHttpChannel.h"
    12 #include "nsError.h"
    13 #include "nsContentUtils.h"
    14 #include "nsIScriptSecurityManager.h"
    15 #include "nsNetUtil.h"
    16 #include "nsMimeTypes.h"
    17 #include "nsIStreamConverterService.h"
    18 #include "nsStringStream.h"
    19 #include "nsGkAtoms.h"
    20 #include "nsWhitespaceTokenizer.h"
    21 #include "nsIChannelEventSink.h"
    22 #include "nsIAsyncVerifyRedirectCallback.h"
    23 #include "nsCharSeparatedTokenizer.h"
    24 #include "nsAsyncRedirectVerifyHelper.h"
    25 #include "nsClassHashtable.h"
    26 #include "nsHashKeys.h"
    27 #include "nsStreamUtils.h"
    28 #include "mozilla/Preferences.h"
    29 #include "nsIScriptError.h"
    30 #include "nsILoadGroup.h"
    31 #include "nsILoadContext.h"
    32 #include "nsIConsoleService.h"
    33 #include "nsIDOMWindowUtils.h"
    34 #include "nsIDOMWindow.h"
    35 #include <algorithm>
    37 using namespace mozilla;
    39 #define PREFLIGHT_CACHE_SIZE 100
    41 static bool gDisableCORS = false;
    42 static bool gDisableCORSPrivateData = false;
    44 static nsresult
    45 LogBlockedRequest(nsIRequest* aRequest)
    46 {
    47   nsresult rv = NS_OK;
    49   // Get the innerWindowID associated with the XMLHTTPRequest
    50   PRUint64 innerWindowID = 0;
    52   nsCOMPtr<nsILoadGroup> loadGroup;
    53   aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
    54   if (loadGroup) {
    55     nsCOMPtr<nsIInterfaceRequestor> callbacks;
    56     loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
    57     if (callbacks) {
    58       nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
    59       if(loadContext) {
    60         nsCOMPtr<nsIDOMWindow> window;
    61         loadContext->GetAssociatedWindow(getter_AddRefs(window));
    62         if (window) {
    63           nsCOMPtr<nsIDOMWindowUtils> du = do_GetInterface(window);
    64           du->GetCurrentInnerWindowID(&innerWindowID);
    65         }
    66       }
    67     }
    68   }
    70   if (!innerWindowID) {
    71     return NS_ERROR_FAILURE;
    72   }
    74   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
    75   nsCOMPtr<nsIURI> aUri;
    76   channel->GetURI(getter_AddRefs(aUri));
    77   nsAutoCString spec;
    78   if (aUri) {
    79     aUri->GetSpec(spec);
    80   }
    82   // Generate the error message
    83   nsXPIDLString blockedMessage;
    84   NS_ConvertUTF8toUTF16 specUTF16(spec);
    85   const char16_t* params[] = { specUTF16.get() };
    86   rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
    87                                              "CrossSiteRequestBlocked",
    88                                              params,
    89                                              blockedMessage);
    91   // Build the error object and log it to the console
    92   nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
    93   NS_ENSURE_SUCCESS(rv, rv);
    95   nsCOMPtr<nsIScriptError> scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
    96   NS_ENSURE_SUCCESS(rv, rv);
    98   nsAutoString msg(blockedMessage.get());
    99   rv = scriptError->InitWithWindowID(msg,
   100                                      NS_ConvertUTF8toUTF16(spec),
   101                                      EmptyString(),
   102                                      0,
   103                                      0,
   104                                      nsIScriptError::warningFlag,
   105                                      "CORS",
   106                                      innerWindowID);
   107   NS_ENSURE_SUCCESS(rv, rv);
   109   rv = console->LogMessage(scriptError);
   110   return rv;
   111 }
   113 //////////////////////////////////////////////////////////////////////////
   114 // Preflight cache
   116 class nsPreflightCache
   117 {
   118 public:
   119   struct TokenTime
   120   {
   121     nsCString token;
   122     TimeStamp expirationTime;
   123   };
   125   struct CacheEntry : public LinkedListElement<CacheEntry>
   126   {
   127     CacheEntry(nsCString& aKey)
   128       : mKey(aKey)
   129     {
   130       MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
   131     }
   133     ~CacheEntry()
   134     {
   135       MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry);
   136     }
   138     void PurgeExpired(TimeStamp now);
   139     bool CheckRequest(const nsCString& aMethod,
   140                         const nsTArray<nsCString>& aCustomHeaders);
   142     nsCString mKey;
   143     nsTArray<TokenTime> mMethods;
   144     nsTArray<TokenTime> mHeaders;
   145   };
   147   nsPreflightCache()
   148   {
   149     MOZ_COUNT_CTOR(nsPreflightCache);
   150   }
   152   ~nsPreflightCache()
   153   {
   154     Clear();
   155     MOZ_COUNT_DTOR(nsPreflightCache);
   156   }
   158   bool Initialize()
   159   {
   160     return true;
   161   }
   163   CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
   164                        bool aWithCredentials, bool aCreate);
   165   void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
   167   void Clear();
   169 private:
   170   static PLDHashOperator
   171     RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
   172                          void* aUserData);
   174   static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
   175                             bool aWithCredentials, nsACString& _retval);
   177   nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
   178   LinkedList<CacheEntry> mList;
   179 };
   181 // Will be initialized in EnsurePreflightCache.
   182 static nsPreflightCache* sPreflightCache = nullptr;
   184 static bool EnsurePreflightCache()
   185 {
   186   if (sPreflightCache)
   187     return true;
   189   nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache());
   191   if (newCache->Initialize()) {
   192     sPreflightCache = newCache.forget();
   193     return true;
   194   }
   196   return false;
   197 }
   199 void
   200 nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now)
   201 {
   202   uint32_t i;
   203   for (i = 0; i < mMethods.Length(); ++i) {
   204     if (now >= mMethods[i].expirationTime) {
   205       mMethods.RemoveElementAt(i--);
   206     }
   207   }
   208   for (i = 0; i < mHeaders.Length(); ++i) {
   209     if (now >= mHeaders[i].expirationTime) {
   210       mHeaders.RemoveElementAt(i--);
   211     }
   212   }
   213 }
   215 bool
   216 nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod,
   217                                            const nsTArray<nsCString>& aHeaders)
   218 {
   219   PurgeExpired(TimeStamp::NowLoRes());
   221   if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
   222     uint32_t i;
   223     for (i = 0; i < mMethods.Length(); ++i) {
   224       if (aMethod.Equals(mMethods[i].token))
   225         break;
   226     }
   227     if (i == mMethods.Length()) {
   228       return false;
   229     }
   230   }
   232   for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
   233     uint32_t j;
   234     for (j = 0; j < mHeaders.Length(); ++j) {
   235       if (aHeaders[i].Equals(mHeaders[j].token,
   236                              nsCaseInsensitiveCStringComparator())) {
   237         break;
   238       }
   239     }
   240     if (j == mHeaders.Length()) {
   241       return false;
   242     }
   243   }
   245   return true;
   246 }
   248 nsPreflightCache::CacheEntry*
   249 nsPreflightCache::GetEntry(nsIURI* aURI,
   250                            nsIPrincipal* aPrincipal,
   251                            bool aWithCredentials,
   252                            bool aCreate)
   253 {
   254   nsCString key;
   255   if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
   256     NS_WARNING("Invalid cache key!");
   257     return nullptr;
   258   }
   260   CacheEntry* entry;
   262   if (mTable.Get(key, &entry)) {
   263     // Entry already existed so just return it. Also update the LRU list.
   265     // Move to the head of the list.
   266     entry->removeFrom(mList);
   267     mList.insertFront(entry);
   269     return entry;
   270   }
   272   if (!aCreate) {
   273     return nullptr;
   274   }
   276   // This is a new entry, allocate and insert into the table now so that any
   277   // failures don't cause items to be removed from a full cache.
   278   entry = new CacheEntry(key);
   279   if (!entry) {
   280     NS_WARNING("Failed to allocate new cache entry!");
   281     return nullptr;
   282   }
   284   NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
   285                "Something is borked, too many entries in the cache!");
   287   // Now enforce the max count.
   288   if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
   289     // Try to kick out all the expired entries.
   290     TimeStamp now = TimeStamp::NowLoRes();
   291     mTable.Enumerate(RemoveExpiredEntries, &now);
   293     // If that didn't remove anything then kick out the least recently used
   294     // entry.
   295     if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
   296       CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
   297       MOZ_ASSERT(lruEntry);
   299       // This will delete 'lruEntry'.
   300       mTable.Remove(lruEntry->mKey);
   302       NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
   303                    "Somehow tried to remove an entry that was never added!");
   304     }
   305   }
   307   mTable.Put(key, entry);
   308   mList.insertFront(entry);
   310   return entry;
   311 }
   313 void
   314 nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
   315 {
   316   CacheEntry* entry;
   317   nsCString key;
   318   if (GetCacheKey(aURI, aPrincipal, true, key) &&
   319       mTable.Get(key, &entry)) {
   320     entry->removeFrom(mList);
   321     mTable.Remove(key);
   322   }
   324   if (GetCacheKey(aURI, aPrincipal, false, key) &&
   325       mTable.Get(key, &entry)) {
   326     entry->removeFrom(mList);
   327     mTable.Remove(key);
   328   }
   329 }
   331 void
   332 nsPreflightCache::Clear()
   333 {
   334   mList.clear();
   335   mTable.Clear();
   336 }
   338 /* static */ PLDHashOperator
   339 nsPreflightCache::RemoveExpiredEntries(const nsACString& aKey,
   340                                            nsAutoPtr<CacheEntry>& aValue,
   341                                            void* aUserData)
   342 {
   343   TimeStamp* now = static_cast<TimeStamp*>(aUserData);
   345   aValue->PurgeExpired(*now);
   347   if (aValue->mHeaders.IsEmpty() &&
   348       aValue->mMethods.IsEmpty()) {
   349     // Expired, remove from the list as well as the hash table.
   350     aValue->removeFrom(sPreflightCache->mList);
   351     return PL_DHASH_REMOVE;
   352   }
   354   return PL_DHASH_NEXT;
   355 }
   357 /* static */ bool
   358 nsPreflightCache::GetCacheKey(nsIURI* aURI,
   359                               nsIPrincipal* aPrincipal,
   360                               bool aWithCredentials,
   361                               nsACString& _retval)
   362 {
   363   NS_ASSERTION(aURI, "Null uri!");
   364   NS_ASSERTION(aPrincipal, "Null principal!");
   366   NS_NAMED_LITERAL_CSTRING(space, " ");
   368   nsCOMPtr<nsIURI> uri;
   369   nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
   370   NS_ENSURE_SUCCESS(rv, false);
   372   nsAutoCString scheme, host, port;
   373   if (uri) {
   374     uri->GetScheme(scheme);
   375     uri->GetHost(host);
   376     port.AppendInt(NS_GetRealPort(uri));
   377   }
   379   nsAutoCString cred;
   380   if (aWithCredentials) {
   381     _retval.AssignLiteral("cred");
   382   }
   383   else {
   384     _retval.AssignLiteral("nocred");
   385   }
   387   nsAutoCString spec;
   388   rv = aURI->GetSpec(spec);
   389   NS_ENSURE_SUCCESS(rv, false);
   391   _retval.Assign(cred + space + scheme + space + host + space + port + space +
   392                  spec);
   394   return true;
   395 }
   397 //////////////////////////////////////////////////////////////////////////
   398 // nsCORSListenerProxy
   400 NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener,
   401                   nsIRequestObserver, nsIChannelEventSink,
   402                   nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
   404 /* static */
   405 void
   406 nsCORSListenerProxy::Startup()
   407 {
   408   Preferences::AddBoolVarCache(&gDisableCORS,
   409                                "content.cors.disable");
   410   Preferences::AddBoolVarCache(&gDisableCORSPrivateData,
   411                                "content.cors.no_private_data");
   412 }
   414 /* static */
   415 void
   416 nsCORSListenerProxy::Shutdown()
   417 {
   418   delete sPreflightCache;
   419   sPreflightCache = nullptr;
   420 }
   422 nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
   423                                          nsIPrincipal* aRequestingPrincipal,
   424                                          bool aWithCredentials)
   425   : mOuterListener(aOuter),
   426     mRequestingPrincipal(aRequestingPrincipal),
   427     mOriginHeaderPrincipal(aRequestingPrincipal),
   428     mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
   429     mRequestApproved(false),
   430     mHasBeenCrossSite(false),
   431     mIsPreflight(false)
   432 {
   433 }
   435 nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
   436                                          nsIPrincipal* aRequestingPrincipal,
   437                                          bool aWithCredentials,
   438                                          const nsCString& aPreflightMethod,
   439                                          const nsTArray<nsCString>& aPreflightHeaders)
   440   : mOuterListener(aOuter),
   441     mRequestingPrincipal(aRequestingPrincipal),
   442     mOriginHeaderPrincipal(aRequestingPrincipal),
   443     mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
   444     mRequestApproved(false),
   445     mHasBeenCrossSite(false),
   446     mIsPreflight(true),
   447     mPreflightMethod(aPreflightMethod),
   448     mPreflightHeaders(aPreflightHeaders)
   449 {
   450   for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   451     ToLowerCase(mPreflightHeaders[i]);
   452   }
   453   mPreflightHeaders.Sort();
   454 }
   456 nsresult
   457 nsCORSListenerProxy::Init(nsIChannel* aChannel, bool aAllowDataURI)
   458 {
   459   aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks));
   460   aChannel->SetNotificationCallbacks(this);
   462   nsresult rv = UpdateChannel(aChannel, aAllowDataURI);
   463   if (NS_FAILED(rv)) {
   464     mOuterListener = nullptr;
   465     mRequestingPrincipal = nullptr;
   466     mOriginHeaderPrincipal = nullptr;
   467     mOuterNotificationCallbacks = nullptr;
   468   }
   469   return rv;
   470 }
   472 NS_IMETHODIMP
   473 nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
   474                                     nsISupports* aContext)
   475 {
   476   nsresult rv = CheckRequestApproved(aRequest);
   477   mRequestApproved = NS_SUCCEEDED(rv);
   478   if (!mRequestApproved) {
   479     rv = LogBlockedRequest(aRequest);
   480     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
   481     if (sPreflightCache) {
   482       nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   483       if (channel) {
   484         nsCOMPtr<nsIURI> uri;
   485         NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
   486         if (uri) {
   487           // OK to use mRequestingPrincipal since preflights never get
   488           // redirected.
   489           sPreflightCache->RemoveEntries(uri, mRequestingPrincipal);
   490         }
   491       }
   492     }
   494     aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
   495     mOuterListener->OnStartRequest(aRequest, aContext);
   497     return NS_ERROR_DOM_BAD_URI;
   498   }
   500   return mOuterListener->OnStartRequest(aRequest, aContext);
   501 }
   503 bool
   504 IsValidHTTPToken(const nsCSubstring& aToken)
   505 {
   506   if (aToken.IsEmpty()) {
   507     return false;
   508   }
   510   nsCSubstring::const_char_iterator iter, end;
   512   aToken.BeginReading(iter);
   513   aToken.EndReading(end);
   515   while (iter != end) {
   516     if (*iter <= 32 ||
   517         *iter >= 127 ||
   518         *iter == '(' ||
   519         *iter == ')' ||
   520         *iter == '<' ||
   521         *iter == '>' ||
   522         *iter == '@' ||
   523         *iter == ',' ||
   524         *iter == ';' ||
   525         *iter == ':' ||
   526         *iter == '\\' ||
   527         *iter == '\"' ||
   528         *iter == '/' ||
   529         *iter == '[' ||
   530         *iter == ']' ||
   531         *iter == '?' ||
   532         *iter == '=' ||
   533         *iter == '{' ||
   534         *iter == '}') {
   535       return false;
   536     }
   537     ++iter;
   538   }
   540   return true;
   541 }
   543 nsresult
   544 nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
   545 {
   546   // Check if this was actually a cross domain request
   547   if (!mHasBeenCrossSite) {
   548     return NS_OK;
   549   }
   551   if (gDisableCORS) {
   552     return NS_ERROR_DOM_BAD_URI;
   553   }
   555   // Check if the request failed
   556   nsresult status;
   557   nsresult rv = aRequest->GetStatus(&status);
   558   NS_ENSURE_SUCCESS(rv, rv);
   559   NS_ENSURE_SUCCESS(status, status);
   561   // Test that things worked on a HTTP level
   562   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   563   NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI);
   565   // Check the Access-Control-Allow-Origin header
   566   nsAutoCString allowedOriginHeader;
   567   rv = http->GetResponseHeader(
   568     NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
   569   NS_ENSURE_SUCCESS(rv, rv);
   571   if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
   572     nsAutoCString origin;
   573     rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
   574     NS_ENSURE_SUCCESS(rv, rv);
   576     if (!allowedOriginHeader.Equals(origin)) {
   577       return NS_ERROR_DOM_BAD_URI;
   578     }
   579   }
   581   // Check Access-Control-Allow-Credentials header
   582   if (mWithCredentials) {
   583     nsAutoCString allowCredentialsHeader;
   584     rv = http->GetResponseHeader(
   585       NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
   586     NS_ENSURE_SUCCESS(rv, rv);
   588     if (!allowCredentialsHeader.EqualsLiteral("true")) {
   589       return NS_ERROR_DOM_BAD_URI;
   590     }
   591   }
   593   if (mIsPreflight) {
   594     bool succeedded;
   595     rv = http->GetRequestSucceeded(&succeedded);
   596     NS_ENSURE_SUCCESS(rv, rv);
   597     if (!succeedded) {
   598       return NS_ERROR_DOM_BAD_URI;
   599     }
   601     nsAutoCString headerVal;
   602     // The "Access-Control-Allow-Methods" header contains a comma separated
   603     // list of method names.
   604     http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
   605                             headerVal);
   606     bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
   607                          mPreflightMethod.EqualsLiteral("HEAD") ||
   608                          mPreflightMethod.EqualsLiteral("POST");
   609     nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
   610     while(methodTokens.hasMoreTokens()) {
   611       const nsDependentCSubstring& method = methodTokens.nextToken();
   612       if (method.IsEmpty()) {
   613         continue;
   614       }
   615       if (!IsValidHTTPToken(method)) {
   616         return NS_ERROR_DOM_BAD_URI;
   617       }
   618       foundMethod |= mPreflightMethod.Equals(method);
   619     }
   620     NS_ENSURE_TRUE(foundMethod, NS_ERROR_DOM_BAD_URI);
   622     // The "Access-Control-Allow-Headers" header contains a comma separated
   623     // list of header names.
   624     headerVal.Truncate();
   625     http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
   626                             headerVal);
   627     nsTArray<nsCString> headers;
   628     nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
   629     while(headerTokens.hasMoreTokens()) {
   630       const nsDependentCSubstring& header = headerTokens.nextToken();
   631       if (header.IsEmpty()) {
   632         continue;
   633       }
   634       if (!IsValidHTTPToken(header)) {
   635         return NS_ERROR_DOM_BAD_URI;
   636       }
   637       headers.AppendElement(header);
   638     }
   639     for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   640       if (!headers.Contains(mPreflightHeaders[i],
   641                             nsCaseInsensitiveCStringArrayComparator())) {
   642         return NS_ERROR_DOM_BAD_URI;
   643       }
   644     }
   645   }
   647   return NS_OK;
   648 }
   650 NS_IMETHODIMP
   651 nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
   652                                    nsISupports* aContext,
   653                                    nsresult aStatusCode)
   654 {
   655   nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
   656   mOuterListener = nullptr;
   657   mOuterNotificationCallbacks = nullptr;
   658   mRedirectCallback = nullptr;
   659   mOldRedirectChannel = nullptr;
   660   mNewRedirectChannel = nullptr;
   661   return rv;
   662 }
   664 NS_IMETHODIMP
   665 nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
   666                                      nsISupports* aContext, 
   667                                      nsIInputStream* aInputStream,
   668                                      uint64_t aOffset,
   669                                      uint32_t aCount)
   670 {
   671   if (!mRequestApproved) {
   672     return NS_ERROR_DOM_BAD_URI;
   673   }
   674   return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream,
   675                                          aOffset, aCount);
   676 }
   678 NS_IMETHODIMP
   679 nsCORSListenerProxy::GetInterface(const nsIID & aIID, void **aResult)
   680 {
   681   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
   682     *aResult = static_cast<nsIChannelEventSink*>(this);
   683     NS_ADDREF_THIS();
   685     return NS_OK;
   686   }
   688   return mOuterNotificationCallbacks ?
   689     mOuterNotificationCallbacks->GetInterface(aIID, aResult) :
   690     NS_ERROR_NO_INTERFACE;
   691 }
   693 NS_IMETHODIMP
   694 nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
   695                                             nsIChannel *aNewChannel,
   696                                             uint32_t aFlags,
   697                                             nsIAsyncVerifyRedirectCallback *cb)
   698 {
   699   nsresult rv;
   700   if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
   701     rv = CheckRequestApproved(aOldChannel);
   702     if (NS_FAILED(rv)) {
   703       rv = LogBlockedRequest(aOldChannel);
   704       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
   706       if (sPreflightCache) {
   707         nsCOMPtr<nsIURI> oldURI;
   708         NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
   709         if (oldURI) {
   710           // OK to use mRequestingPrincipal since preflights never get
   711           // redirected.
   712           sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal);
   713         }
   714       }
   715       aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
   716       return NS_ERROR_DOM_BAD_URI;
   717     }
   719     if (mHasBeenCrossSite) {
   720       // Once we've been cross-site, cross-origin redirects reset our source
   721       // origin.
   722       nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
   723       nsContentUtils::GetSecurityManager()->
   724         GetChannelPrincipal(aOldChannel, getter_AddRefs(oldChannelPrincipal));
   725       nsCOMPtr<nsIPrincipal> newChannelPrincipal;
   726       nsContentUtils::GetSecurityManager()->
   727         GetChannelPrincipal(aNewChannel, getter_AddRefs(newChannelPrincipal));
   728       if (!oldChannelPrincipal || !newChannelPrincipal) {
   729         rv = NS_ERROR_OUT_OF_MEMORY;
   730       }
   732       if (NS_SUCCEEDED(rv)) {
   733         bool equal;
   734         rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal);
   735         if (NS_SUCCEEDED(rv)) {
   736           if (!equal) {
   737             // Spec says to set our source origin to a unique origin.
   738             mOriginHeaderPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
   739             if (!mOriginHeaderPrincipal) {
   740               rv = NS_ERROR_OUT_OF_MEMORY;
   741             }
   742           }
   743         }
   744       }
   746       if (NS_FAILED(rv)) {
   747         aOldChannel->Cancel(rv);
   748         return rv;
   749       }
   750     }
   751   }
   753   // Prepare to receive callback
   754   mRedirectCallback = cb;
   755   mOldRedirectChannel = aOldChannel;
   756   mNewRedirectChannel = aNewChannel;
   758   nsCOMPtr<nsIChannelEventSink> outer =
   759     do_GetInterface(mOuterNotificationCallbacks);
   760   if (outer) {
   761     rv = outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, this);
   762     if (NS_FAILED(rv)) {
   763         aOldChannel->Cancel(rv); // is this necessary...?
   764         mRedirectCallback = nullptr;
   765         mOldRedirectChannel = nullptr;
   766         mNewRedirectChannel = nullptr;
   767     }
   768     return rv;  
   769   }
   771   (void) OnRedirectVerifyCallback(NS_OK);
   772   return NS_OK;
   773 }
   775 NS_IMETHODIMP
   776 nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result)
   777 {
   778   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   779   NS_ASSERTION(mOldRedirectChannel, "mOldRedirectChannel not set in callback");
   780   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
   782   if (NS_SUCCEEDED(result)) {
   783       nsresult rv = UpdateChannel(mNewRedirectChannel);
   784       if (NS_FAILED(rv)) {
   785           NS_WARNING("nsCORSListenerProxy::OnRedirectVerifyCallback: "
   786                      "UpdateChannel() returned failure");
   787       }
   788       result = rv;
   789   }
   791   if (NS_FAILED(result)) {
   792     mOldRedirectChannel->Cancel(result);
   793   }
   795   mOldRedirectChannel = nullptr;
   796   mNewRedirectChannel = nullptr;
   797   mRedirectCallback->OnRedirectVerifyCallback(result);
   798   mRedirectCallback   = nullptr;
   799   return NS_OK;
   800 }
   802 nsresult
   803 nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI)
   804 {
   805   nsCOMPtr<nsIURI> uri, originalURI;
   806   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   807   NS_ENSURE_SUCCESS(rv, rv);
   808   rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
   809   NS_ENSURE_SUCCESS(rv, rv);
   811   // exempt data URIs from the same origin check.
   812   if (aAllowDataURI && originalURI == uri) {
   813     bool dataScheme = false;
   814     rv = uri->SchemeIs("data", &dataScheme);
   815     NS_ENSURE_SUCCESS(rv, rv);
   816     if (dataScheme) {
   817       return NS_OK;
   818     }
   819   }
   821   // Check that the uri is ok to load
   822   rv = nsContentUtils::GetSecurityManager()->
   823     CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
   824                               nsIScriptSecurityManager::STANDARD);
   825   NS_ENSURE_SUCCESS(rv, rv);
   827   if (originalURI != uri) {
   828     rv = nsContentUtils::GetSecurityManager()->
   829       CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI,
   830                                 nsIScriptSecurityManager::STANDARD);
   831     NS_ENSURE_SUCCESS(rv, rv);
   832   }
   834   if (!mHasBeenCrossSite &&
   835       NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) &&
   836       (originalURI == uri ||
   837        NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI,
   838                                                        false, false)))) {
   839     return NS_OK;
   840   }
   842   // It's a cross site load
   843   mHasBeenCrossSite = true;
   845   nsCString userpass;
   846   uri->GetUserPass(userpass);
   847   NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
   849   // Add the Origin header
   850   nsAutoCString origin;
   851   rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
   852   NS_ENSURE_SUCCESS(rv, rv);
   854   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
   855   NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
   857   rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false);
   858   NS_ENSURE_SUCCESS(rv, rv);
   860   // Add preflight headers if this is a preflight request
   861   if (mIsPreflight) {
   862     rv = http->
   863       SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
   864                        mPreflightMethod, false);
   865     NS_ENSURE_SUCCESS(rv, rv);
   867     if (!mPreflightHeaders.IsEmpty()) {
   868       nsAutoCString headers;
   869       for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   870         if (i != 0) {
   871           headers += ',';
   872         }
   873         headers += mPreflightHeaders[i];
   874       }
   875       rv = http->
   876         SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
   877                          headers, false);
   878       NS_ENSURE_SUCCESS(rv, rv);
   879     }
   880   }
   882   // Make cookie-less if needed
   883   if (mIsPreflight || !mWithCredentials) {
   884     nsLoadFlags flags;
   885     rv = http->GetLoadFlags(&flags);
   886     NS_ENSURE_SUCCESS(rv, rv);
   888     flags |= nsIRequest::LOAD_ANONYMOUS;
   889     rv = http->SetLoadFlags(flags);
   890     NS_ENSURE_SUCCESS(rv, rv);
   891   }
   893   return NS_OK;
   894 }
   896 //////////////////////////////////////////////////////////////////////////
   897 // Preflight proxy
   899 // Class used as streamlistener and notification callback when
   900 // doing the initial OPTIONS request for a CORS check
   901 class nsCORSPreflightListener MOZ_FINAL : public nsIStreamListener,
   902                                           public nsIInterfaceRequestor,
   903                                           public nsIChannelEventSink
   904 {
   905 public:
   906   nsCORSPreflightListener(nsIChannel* aOuterChannel,
   907                           nsIStreamListener* aOuterListener,
   908                           nsISupports* aOuterContext,
   909                           nsIPrincipal* aReferrerPrincipal,
   910                           const nsACString& aRequestMethod,
   911                           bool aWithCredentials)
   912    : mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
   913      mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
   914      mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials)
   915   { }
   917   NS_DECL_ISUPPORTS
   918   NS_DECL_NSISTREAMLISTENER
   919   NS_DECL_NSIREQUESTOBSERVER
   920   NS_DECL_NSIINTERFACEREQUESTOR
   921   NS_DECL_NSICHANNELEVENTSINK
   923 private:
   924   void AddResultToCache(nsIRequest* aRequest);
   926   nsCOMPtr<nsIChannel> mOuterChannel;
   927   nsCOMPtr<nsIStreamListener> mOuterListener;
   928   nsCOMPtr<nsISupports> mOuterContext;
   929   nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
   930   nsCString mRequestMethod;
   931   bool mWithCredentials;
   932 };
   934 NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
   935                   nsIRequestObserver, nsIInterfaceRequestor,
   936                   nsIChannelEventSink)
   938 void
   939 nsCORSPreflightListener::AddResultToCache(nsIRequest *aRequest)
   940 {
   941   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   942   NS_ASSERTION(http, "Request was not http");
   944   // The "Access-Control-Max-Age" header should return an age in seconds.
   945   nsAutoCString headerVal;
   946   http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
   947                           headerVal);
   948   if (headerVal.IsEmpty()) {
   949     return;
   950   }
   952   // Sanitize the string. We only allow 'delta-seconds' as specified by
   953   // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
   954   // trailing non-whitespace characters).
   955   uint32_t age = 0;
   956   nsCSubstring::const_char_iterator iter, end;
   957   headerVal.BeginReading(iter);
   958   headerVal.EndReading(end);
   959   while (iter != end) {
   960     if (*iter < '0' || *iter > '9') {
   961       return;
   962     }
   963     age = age * 10 + (*iter - '0');
   964     // Cap at 24 hours. This also avoids overflow
   965     age = std::min(age, 86400U);
   966     ++iter;
   967   }
   969   if (!age || !EnsurePreflightCache()) {
   970     return;
   971   }
   974   // String seems fine, go ahead and cache.
   975   // Note that we have already checked that these headers follow the correct
   976   // syntax.
   978   nsCOMPtr<nsIURI> uri;
   979   NS_GetFinalChannelURI(http, getter_AddRefs(uri));
   981   TimeStamp expirationTime = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
   983   nsPreflightCache::CacheEntry* entry =
   984     sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials,
   985                               true);
   986   if (!entry) {
   987     return;
   988   }
   990   // The "Access-Control-Allow-Methods" header contains a comma separated
   991   // list of method names.
   992   http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
   993                           headerVal);
   995   nsCCharSeparatedTokenizer methods(headerVal, ',');
   996   while(methods.hasMoreTokens()) {
   997     const nsDependentCSubstring& method = methods.nextToken();
   998     if (method.IsEmpty()) {
   999       continue;
  1001     uint32_t i;
  1002     for (i = 0; i < entry->mMethods.Length(); ++i) {
  1003       if (entry->mMethods[i].token.Equals(method)) {
  1004         entry->mMethods[i].expirationTime = expirationTime;
  1005         break;
  1008     if (i == entry->mMethods.Length()) {
  1009       nsPreflightCache::TokenTime* newMethod =
  1010         entry->mMethods.AppendElement();
  1011       if (!newMethod) {
  1012         return;
  1015       newMethod->token = method;
  1016       newMethod->expirationTime = expirationTime;
  1020   // The "Access-Control-Allow-Headers" header contains a comma separated
  1021   // list of method names.
  1022   http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
  1023                           headerVal);
  1025   nsCCharSeparatedTokenizer headers(headerVal, ',');
  1026   while(headers.hasMoreTokens()) {
  1027     const nsDependentCSubstring& header = headers.nextToken();
  1028     if (header.IsEmpty()) {
  1029       continue;
  1031     uint32_t i;
  1032     for (i = 0; i < entry->mHeaders.Length(); ++i) {
  1033       if (entry->mHeaders[i].token.Equals(header)) {
  1034         entry->mHeaders[i].expirationTime = expirationTime;
  1035         break;
  1038     if (i == entry->mHeaders.Length()) {
  1039       nsPreflightCache::TokenTime* newHeader =
  1040         entry->mHeaders.AppendElement();
  1041       if (!newHeader) {
  1042         return;
  1045       newHeader->token = header;
  1046       newHeader->expirationTime = expirationTime;
  1051 NS_IMETHODIMP
  1052 nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest,
  1053                                         nsISupports *aContext)
  1055   nsresult status;
  1056   nsresult rv = aRequest->GetStatus(&status);
  1058   if (NS_SUCCEEDED(rv)) {
  1059     rv = status;
  1062   if (NS_SUCCEEDED(rv)) {
  1063     // Everything worked, try to cache and then fire off the actual request.
  1064     AddResultToCache(aRequest);
  1066     rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
  1069   if (NS_FAILED(rv)) {
  1070     mOuterChannel->Cancel(rv);
  1071     mOuterListener->OnStartRequest(mOuterChannel, mOuterContext);
  1072     mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv);
  1074     return rv;
  1077   return NS_OK;
  1080 NS_IMETHODIMP
  1081 nsCORSPreflightListener::OnStopRequest(nsIRequest *aRequest,
  1082                                        nsISupports *aContext,
  1083                                        nsresult aStatus)
  1085   mOuterChannel = nullptr;
  1086   mOuterListener = nullptr;
  1087   mOuterContext = nullptr;
  1088   return NS_OK;
  1091 /** nsIStreamListener methods **/
  1093 NS_IMETHODIMP
  1094 nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest,
  1095                                          nsISupports *ctxt,
  1096                                          nsIInputStream *inStr,
  1097                                          uint64_t sourceOffset,
  1098                                          uint32_t count)
  1100   uint32_t totalRead;
  1101   return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
  1104 NS_IMETHODIMP
  1105 nsCORSPreflightListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
  1106                                                 nsIChannel *aNewChannel,
  1107                                                 uint32_t aFlags,
  1108                                                 nsIAsyncVerifyRedirectCallback *callback)
  1110   // Only internal redirects allowed for now.
  1111   if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags))
  1112     return NS_ERROR_DOM_BAD_URI;
  1114   callback->OnRedirectVerifyCallback(NS_OK);
  1115   return NS_OK;
  1118 NS_IMETHODIMP
  1119 nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult)
  1121   return QueryInterface(aIID, aResult);
  1125 nsresult
  1126 NS_StartCORSPreflight(nsIChannel* aRequestChannel,
  1127                       nsIStreamListener* aListener,
  1128                       nsIPrincipal* aPrincipal,
  1129                       bool aWithCredentials,
  1130                       nsTArray<nsCString>& aUnsafeHeaders,
  1131                       nsIChannel** aPreflightChannel)
  1133   *aPreflightChannel = nullptr;
  1135   nsAutoCString method;
  1136   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
  1137   NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
  1138   httpChannel->GetRequestMethod(method);
  1140   nsCOMPtr<nsIURI> uri;
  1141   nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
  1142   NS_ENSURE_SUCCESS(rv, rv);
  1144   nsPreflightCache::CacheEntry* entry =
  1145     sPreflightCache ?
  1146     sPreflightCache->GetEntry(uri, aPrincipal, aWithCredentials, false) :
  1147     nullptr;
  1149   if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
  1150     // We have a cached preflight result, just start the original channel
  1151     return aRequestChannel->AsyncOpen(aListener, nullptr);
  1154   // Either it wasn't cached or the cached result has expired. Build a
  1155   // channel for the OPTIONS request.
  1157   nsCOMPtr<nsILoadGroup> loadGroup;
  1158   rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  1159   NS_ENSURE_SUCCESS(rv, rv);
  1161   nsLoadFlags loadFlags;
  1162   rv = aRequestChannel->GetLoadFlags(&loadFlags);
  1163   NS_ENSURE_SUCCESS(rv, rv);
  1165   nsCOMPtr<nsIChannel> preflightChannel;
  1166   rv = NS_NewChannel(getter_AddRefs(preflightChannel), uri, nullptr,
  1167                      loadGroup, nullptr, loadFlags);
  1168   NS_ENSURE_SUCCESS(rv, rv);
  1170   nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
  1171   NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
  1173   rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
  1174   NS_ENSURE_SUCCESS(rv, rv);
  1176   // Set up listener which will start the original channel
  1177   nsCOMPtr<nsIStreamListener> preflightListener =
  1178     new nsCORSPreflightListener(aRequestChannel, aListener, nullptr, aPrincipal,
  1179                                 method, aWithCredentials);
  1180   NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY);
  1182   nsRefPtr<nsCORSListenerProxy> corsListener =
  1183     new nsCORSListenerProxy(preflightListener, aPrincipal,
  1184                             aWithCredentials, method,
  1185                             aUnsafeHeaders);
  1186   rv = corsListener->Init(preflightChannel);
  1187   NS_ENSURE_SUCCESS(rv, rv);
  1188   preflightListener = corsListener;
  1190   // Start preflight
  1191   rv = preflightChannel->AsyncOpen(preflightListener, nullptr);
  1192   NS_ENSURE_SUCCESS(rv, rv);
  1194   // Return newly created preflight channel
  1195   preflightChannel.forget(aPreflightChannel);
  1197   return NS_OK;

mercurial