content/base/src/nsCrossSiteListenerProxy.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/base/src/nsCrossSiteListenerProxy.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1199 @@
     1.4 +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "mozilla/Assertions.h"
    1.10 +#include "mozilla/LinkedList.h"
    1.11 +
    1.12 +#include "nsCrossSiteListenerProxy.h"
    1.13 +#include "nsIChannel.h"
    1.14 +#include "nsIHttpChannel.h"
    1.15 +#include "nsError.h"
    1.16 +#include "nsContentUtils.h"
    1.17 +#include "nsIScriptSecurityManager.h"
    1.18 +#include "nsNetUtil.h"
    1.19 +#include "nsMimeTypes.h"
    1.20 +#include "nsIStreamConverterService.h"
    1.21 +#include "nsStringStream.h"
    1.22 +#include "nsGkAtoms.h"
    1.23 +#include "nsWhitespaceTokenizer.h"
    1.24 +#include "nsIChannelEventSink.h"
    1.25 +#include "nsIAsyncVerifyRedirectCallback.h"
    1.26 +#include "nsCharSeparatedTokenizer.h"
    1.27 +#include "nsAsyncRedirectVerifyHelper.h"
    1.28 +#include "nsClassHashtable.h"
    1.29 +#include "nsHashKeys.h"
    1.30 +#include "nsStreamUtils.h"
    1.31 +#include "mozilla/Preferences.h"
    1.32 +#include "nsIScriptError.h"
    1.33 +#include "nsILoadGroup.h"
    1.34 +#include "nsILoadContext.h"
    1.35 +#include "nsIConsoleService.h"
    1.36 +#include "nsIDOMWindowUtils.h"
    1.37 +#include "nsIDOMWindow.h"
    1.38 +#include <algorithm>
    1.39 +
    1.40 +using namespace mozilla;
    1.41 +
    1.42 +#define PREFLIGHT_CACHE_SIZE 100
    1.43 +
    1.44 +static bool gDisableCORS = false;
    1.45 +static bool gDisableCORSPrivateData = false;
    1.46 +
    1.47 +static nsresult
    1.48 +LogBlockedRequest(nsIRequest* aRequest)
    1.49 +{
    1.50 +  nsresult rv = NS_OK;
    1.51 +
    1.52 +  // Get the innerWindowID associated with the XMLHTTPRequest
    1.53 +  PRUint64 innerWindowID = 0;
    1.54 +
    1.55 +  nsCOMPtr<nsILoadGroup> loadGroup;
    1.56 +  aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
    1.57 +  if (loadGroup) {
    1.58 +    nsCOMPtr<nsIInterfaceRequestor> callbacks;
    1.59 +    loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
    1.60 +    if (callbacks) {
    1.61 +      nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
    1.62 +      if(loadContext) {
    1.63 +        nsCOMPtr<nsIDOMWindow> window;
    1.64 +        loadContext->GetAssociatedWindow(getter_AddRefs(window));
    1.65 +        if (window) {
    1.66 +          nsCOMPtr<nsIDOMWindowUtils> du = do_GetInterface(window);
    1.67 +          du->GetCurrentInnerWindowID(&innerWindowID);
    1.68 +        }
    1.69 +      }
    1.70 +    }
    1.71 +  }
    1.72 +
    1.73 +  if (!innerWindowID) {
    1.74 +    return NS_ERROR_FAILURE;
    1.75 +  }
    1.76 +
    1.77 +  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
    1.78 +  nsCOMPtr<nsIURI> aUri;
    1.79 +  channel->GetURI(getter_AddRefs(aUri));
    1.80 +  nsAutoCString spec;
    1.81 +  if (aUri) {
    1.82 +    aUri->GetSpec(spec);
    1.83 +  }
    1.84 +
    1.85 +  // Generate the error message
    1.86 +  nsXPIDLString blockedMessage;
    1.87 +  NS_ConvertUTF8toUTF16 specUTF16(spec);
    1.88 +  const char16_t* params[] = { specUTF16.get() };
    1.89 +  rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
    1.90 +                                             "CrossSiteRequestBlocked",
    1.91 +                                             params,
    1.92 +                                             blockedMessage);
    1.93 +
    1.94 +  // Build the error object and log it to the console
    1.95 +  nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
    1.96 +  NS_ENSURE_SUCCESS(rv, rv);
    1.97 +
    1.98 +  nsCOMPtr<nsIScriptError> scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
    1.99 +  NS_ENSURE_SUCCESS(rv, rv);
   1.100 +
   1.101 +  nsAutoString msg(blockedMessage.get());
   1.102 +  rv = scriptError->InitWithWindowID(msg,
   1.103 +                                     NS_ConvertUTF8toUTF16(spec),
   1.104 +                                     EmptyString(),
   1.105 +                                     0,
   1.106 +                                     0,
   1.107 +                                     nsIScriptError::warningFlag,
   1.108 +                                     "CORS",
   1.109 +                                     innerWindowID);
   1.110 +  NS_ENSURE_SUCCESS(rv, rv);
   1.111 +
   1.112 +  rv = console->LogMessage(scriptError);
   1.113 +  return rv;
   1.114 +}
   1.115 +
   1.116 +//////////////////////////////////////////////////////////////////////////
   1.117 +// Preflight cache
   1.118 +
   1.119 +class nsPreflightCache
   1.120 +{
   1.121 +public:
   1.122 +  struct TokenTime
   1.123 +  {
   1.124 +    nsCString token;
   1.125 +    TimeStamp expirationTime;
   1.126 +  };
   1.127 +
   1.128 +  struct CacheEntry : public LinkedListElement<CacheEntry>
   1.129 +  {
   1.130 +    CacheEntry(nsCString& aKey)
   1.131 +      : mKey(aKey)
   1.132 +    {
   1.133 +      MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
   1.134 +    }
   1.135 +    
   1.136 +    ~CacheEntry()
   1.137 +    {
   1.138 +      MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry);
   1.139 +    }
   1.140 +
   1.141 +    void PurgeExpired(TimeStamp now);
   1.142 +    bool CheckRequest(const nsCString& aMethod,
   1.143 +                        const nsTArray<nsCString>& aCustomHeaders);
   1.144 +
   1.145 +    nsCString mKey;
   1.146 +    nsTArray<TokenTime> mMethods;
   1.147 +    nsTArray<TokenTime> mHeaders;
   1.148 +  };
   1.149 +
   1.150 +  nsPreflightCache()
   1.151 +  {
   1.152 +    MOZ_COUNT_CTOR(nsPreflightCache);
   1.153 +  }
   1.154 +
   1.155 +  ~nsPreflightCache()
   1.156 +  {
   1.157 +    Clear();
   1.158 +    MOZ_COUNT_DTOR(nsPreflightCache);
   1.159 +  }
   1.160 +
   1.161 +  bool Initialize()
   1.162 +  {
   1.163 +    return true;
   1.164 +  }
   1.165 +
   1.166 +  CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
   1.167 +                       bool aWithCredentials, bool aCreate);
   1.168 +  void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
   1.169 +
   1.170 +  void Clear();
   1.171 +
   1.172 +private:
   1.173 +  static PLDHashOperator
   1.174 +    RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
   1.175 +                         void* aUserData);
   1.176 +
   1.177 +  static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
   1.178 +                            bool aWithCredentials, nsACString& _retval);
   1.179 +
   1.180 +  nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
   1.181 +  LinkedList<CacheEntry> mList;
   1.182 +};
   1.183 +
   1.184 +// Will be initialized in EnsurePreflightCache.
   1.185 +static nsPreflightCache* sPreflightCache = nullptr;
   1.186 +
   1.187 +static bool EnsurePreflightCache()
   1.188 +{
   1.189 +  if (sPreflightCache)
   1.190 +    return true;
   1.191 +
   1.192 +  nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache());
   1.193 +
   1.194 +  if (newCache->Initialize()) {
   1.195 +    sPreflightCache = newCache.forget();
   1.196 +    return true;
   1.197 +  }
   1.198 +
   1.199 +  return false;
   1.200 +}
   1.201 +
   1.202 +void
   1.203 +nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now)
   1.204 +{
   1.205 +  uint32_t i;
   1.206 +  for (i = 0; i < mMethods.Length(); ++i) {
   1.207 +    if (now >= mMethods[i].expirationTime) {
   1.208 +      mMethods.RemoveElementAt(i--);
   1.209 +    }
   1.210 +  }
   1.211 +  for (i = 0; i < mHeaders.Length(); ++i) {
   1.212 +    if (now >= mHeaders[i].expirationTime) {
   1.213 +      mHeaders.RemoveElementAt(i--);
   1.214 +    }
   1.215 +  }
   1.216 +}
   1.217 +
   1.218 +bool
   1.219 +nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod,
   1.220 +                                           const nsTArray<nsCString>& aHeaders)
   1.221 +{
   1.222 +  PurgeExpired(TimeStamp::NowLoRes());
   1.223 +
   1.224 +  if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
   1.225 +    uint32_t i;
   1.226 +    for (i = 0; i < mMethods.Length(); ++i) {
   1.227 +      if (aMethod.Equals(mMethods[i].token))
   1.228 +        break;
   1.229 +    }
   1.230 +    if (i == mMethods.Length()) {
   1.231 +      return false;
   1.232 +    }
   1.233 +  }
   1.234 +
   1.235 +  for (uint32_t i = 0; i < aHeaders.Length(); ++i) {
   1.236 +    uint32_t j;
   1.237 +    for (j = 0; j < mHeaders.Length(); ++j) {
   1.238 +      if (aHeaders[i].Equals(mHeaders[j].token,
   1.239 +                             nsCaseInsensitiveCStringComparator())) {
   1.240 +        break;
   1.241 +      }
   1.242 +    }
   1.243 +    if (j == mHeaders.Length()) {
   1.244 +      return false;
   1.245 +    }
   1.246 +  }
   1.247 +
   1.248 +  return true;
   1.249 +}
   1.250 +
   1.251 +nsPreflightCache::CacheEntry*
   1.252 +nsPreflightCache::GetEntry(nsIURI* aURI,
   1.253 +                           nsIPrincipal* aPrincipal,
   1.254 +                           bool aWithCredentials,
   1.255 +                           bool aCreate)
   1.256 +{
   1.257 +  nsCString key;
   1.258 +  if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
   1.259 +    NS_WARNING("Invalid cache key!");
   1.260 +    return nullptr;
   1.261 +  }
   1.262 +
   1.263 +  CacheEntry* entry;
   1.264 +
   1.265 +  if (mTable.Get(key, &entry)) {
   1.266 +    // Entry already existed so just return it. Also update the LRU list.
   1.267 +
   1.268 +    // Move to the head of the list.
   1.269 +    entry->removeFrom(mList);
   1.270 +    mList.insertFront(entry);
   1.271 +
   1.272 +    return entry;
   1.273 +  }
   1.274 +
   1.275 +  if (!aCreate) {
   1.276 +    return nullptr;
   1.277 +  }
   1.278 +
   1.279 +  // This is a new entry, allocate and insert into the table now so that any
   1.280 +  // failures don't cause items to be removed from a full cache.
   1.281 +  entry = new CacheEntry(key);
   1.282 +  if (!entry) {
   1.283 +    NS_WARNING("Failed to allocate new cache entry!");
   1.284 +    return nullptr;
   1.285 +  }
   1.286 +
   1.287 +  NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE,
   1.288 +               "Something is borked, too many entries in the cache!");
   1.289 +
   1.290 +  // Now enforce the max count.
   1.291 +  if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
   1.292 +    // Try to kick out all the expired entries.
   1.293 +    TimeStamp now = TimeStamp::NowLoRes();
   1.294 +    mTable.Enumerate(RemoveExpiredEntries, &now);
   1.295 +
   1.296 +    // If that didn't remove anything then kick out the least recently used
   1.297 +    // entry.
   1.298 +    if (mTable.Count() == PREFLIGHT_CACHE_SIZE) {
   1.299 +      CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast());
   1.300 +      MOZ_ASSERT(lruEntry);
   1.301 +
   1.302 +      // This will delete 'lruEntry'.
   1.303 +      mTable.Remove(lruEntry->mKey);
   1.304 +
   1.305 +      NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1,
   1.306 +                   "Somehow tried to remove an entry that was never added!");
   1.307 +    }
   1.308 +  }
   1.309 +  
   1.310 +  mTable.Put(key, entry);
   1.311 +  mList.insertFront(entry);
   1.312 +
   1.313 +  return entry;
   1.314 +}
   1.315 +
   1.316 +void
   1.317 +nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
   1.318 +{
   1.319 +  CacheEntry* entry;
   1.320 +  nsCString key;
   1.321 +  if (GetCacheKey(aURI, aPrincipal, true, key) &&
   1.322 +      mTable.Get(key, &entry)) {
   1.323 +    entry->removeFrom(mList);
   1.324 +    mTable.Remove(key);
   1.325 +  }
   1.326 +
   1.327 +  if (GetCacheKey(aURI, aPrincipal, false, key) &&
   1.328 +      mTable.Get(key, &entry)) {
   1.329 +    entry->removeFrom(mList);
   1.330 +    mTable.Remove(key);
   1.331 +  }
   1.332 +}
   1.333 +
   1.334 +void
   1.335 +nsPreflightCache::Clear()
   1.336 +{
   1.337 +  mList.clear();
   1.338 +  mTable.Clear();
   1.339 +}
   1.340 +
   1.341 +/* static */ PLDHashOperator
   1.342 +nsPreflightCache::RemoveExpiredEntries(const nsACString& aKey,
   1.343 +                                           nsAutoPtr<CacheEntry>& aValue,
   1.344 +                                           void* aUserData)
   1.345 +{
   1.346 +  TimeStamp* now = static_cast<TimeStamp*>(aUserData);
   1.347 +  
   1.348 +  aValue->PurgeExpired(*now);
   1.349 +  
   1.350 +  if (aValue->mHeaders.IsEmpty() &&
   1.351 +      aValue->mMethods.IsEmpty()) {
   1.352 +    // Expired, remove from the list as well as the hash table.
   1.353 +    aValue->removeFrom(sPreflightCache->mList);
   1.354 +    return PL_DHASH_REMOVE;
   1.355 +  }
   1.356 +  
   1.357 +  return PL_DHASH_NEXT;
   1.358 +}
   1.359 +
   1.360 +/* static */ bool
   1.361 +nsPreflightCache::GetCacheKey(nsIURI* aURI,
   1.362 +                              nsIPrincipal* aPrincipal,
   1.363 +                              bool aWithCredentials,
   1.364 +                              nsACString& _retval)
   1.365 +{
   1.366 +  NS_ASSERTION(aURI, "Null uri!");
   1.367 +  NS_ASSERTION(aPrincipal, "Null principal!");
   1.368 +  
   1.369 +  NS_NAMED_LITERAL_CSTRING(space, " ");
   1.370 +
   1.371 +  nsCOMPtr<nsIURI> uri;
   1.372 +  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
   1.373 +  NS_ENSURE_SUCCESS(rv, false);
   1.374 +  
   1.375 +  nsAutoCString scheme, host, port;
   1.376 +  if (uri) {
   1.377 +    uri->GetScheme(scheme);
   1.378 +    uri->GetHost(host);
   1.379 +    port.AppendInt(NS_GetRealPort(uri));
   1.380 +  }
   1.381 +
   1.382 +  nsAutoCString cred;
   1.383 +  if (aWithCredentials) {
   1.384 +    _retval.AssignLiteral("cred");
   1.385 +  }
   1.386 +  else {
   1.387 +    _retval.AssignLiteral("nocred");
   1.388 +  }
   1.389 +
   1.390 +  nsAutoCString spec;
   1.391 +  rv = aURI->GetSpec(spec);
   1.392 +  NS_ENSURE_SUCCESS(rv, false);
   1.393 +
   1.394 +  _retval.Assign(cred + space + scheme + space + host + space + port + space +
   1.395 +                 spec);
   1.396 +
   1.397 +  return true;
   1.398 +}
   1.399 +
   1.400 +//////////////////////////////////////////////////////////////////////////
   1.401 +// nsCORSListenerProxy
   1.402 +
   1.403 +NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener,
   1.404 +                  nsIRequestObserver, nsIChannelEventSink,
   1.405 +                  nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
   1.406 +
   1.407 +/* static */
   1.408 +void
   1.409 +nsCORSListenerProxy::Startup()
   1.410 +{
   1.411 +  Preferences::AddBoolVarCache(&gDisableCORS,
   1.412 +                               "content.cors.disable");
   1.413 +  Preferences::AddBoolVarCache(&gDisableCORSPrivateData,
   1.414 +                               "content.cors.no_private_data");
   1.415 +}
   1.416 +
   1.417 +/* static */
   1.418 +void
   1.419 +nsCORSListenerProxy::Shutdown()
   1.420 +{
   1.421 +  delete sPreflightCache;
   1.422 +  sPreflightCache = nullptr;
   1.423 +}
   1.424 +
   1.425 +nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
   1.426 +                                         nsIPrincipal* aRequestingPrincipal,
   1.427 +                                         bool aWithCredentials)
   1.428 +  : mOuterListener(aOuter),
   1.429 +    mRequestingPrincipal(aRequestingPrincipal),
   1.430 +    mOriginHeaderPrincipal(aRequestingPrincipal),
   1.431 +    mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
   1.432 +    mRequestApproved(false),
   1.433 +    mHasBeenCrossSite(false),
   1.434 +    mIsPreflight(false)
   1.435 +{
   1.436 +}
   1.437 +
   1.438 +nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
   1.439 +                                         nsIPrincipal* aRequestingPrincipal,
   1.440 +                                         bool aWithCredentials,
   1.441 +                                         const nsCString& aPreflightMethod,
   1.442 +                                         const nsTArray<nsCString>& aPreflightHeaders)
   1.443 +  : mOuterListener(aOuter),
   1.444 +    mRequestingPrincipal(aRequestingPrincipal),
   1.445 +    mOriginHeaderPrincipal(aRequestingPrincipal),
   1.446 +    mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
   1.447 +    mRequestApproved(false),
   1.448 +    mHasBeenCrossSite(false),
   1.449 +    mIsPreflight(true),
   1.450 +    mPreflightMethod(aPreflightMethod),
   1.451 +    mPreflightHeaders(aPreflightHeaders)
   1.452 +{
   1.453 +  for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   1.454 +    ToLowerCase(mPreflightHeaders[i]);
   1.455 +  }
   1.456 +  mPreflightHeaders.Sort();
   1.457 +}
   1.458 +
   1.459 +nsresult
   1.460 +nsCORSListenerProxy::Init(nsIChannel* aChannel, bool aAllowDataURI)
   1.461 +{
   1.462 +  aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks));
   1.463 +  aChannel->SetNotificationCallbacks(this);
   1.464 +
   1.465 +  nsresult rv = UpdateChannel(aChannel, aAllowDataURI);
   1.466 +  if (NS_FAILED(rv)) {
   1.467 +    mOuterListener = nullptr;
   1.468 +    mRequestingPrincipal = nullptr;
   1.469 +    mOriginHeaderPrincipal = nullptr;
   1.470 +    mOuterNotificationCallbacks = nullptr;
   1.471 +  }
   1.472 +  return rv;
   1.473 +}
   1.474 +
   1.475 +NS_IMETHODIMP
   1.476 +nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
   1.477 +                                    nsISupports* aContext)
   1.478 +{
   1.479 +  nsresult rv = CheckRequestApproved(aRequest);
   1.480 +  mRequestApproved = NS_SUCCEEDED(rv);
   1.481 +  if (!mRequestApproved) {
   1.482 +    rv = LogBlockedRequest(aRequest);
   1.483 +    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
   1.484 +    if (sPreflightCache) {
   1.485 +      nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   1.486 +      if (channel) {
   1.487 +        nsCOMPtr<nsIURI> uri;
   1.488 +        NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
   1.489 +        if (uri) {
   1.490 +          // OK to use mRequestingPrincipal since preflights never get
   1.491 +          // redirected.
   1.492 +          sPreflightCache->RemoveEntries(uri, mRequestingPrincipal);
   1.493 +        }
   1.494 +      }
   1.495 +    }
   1.496 +
   1.497 +    aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
   1.498 +    mOuterListener->OnStartRequest(aRequest, aContext);
   1.499 +
   1.500 +    return NS_ERROR_DOM_BAD_URI;
   1.501 +  }
   1.502 +
   1.503 +  return mOuterListener->OnStartRequest(aRequest, aContext);
   1.504 +}
   1.505 +
   1.506 +bool
   1.507 +IsValidHTTPToken(const nsCSubstring& aToken)
   1.508 +{
   1.509 +  if (aToken.IsEmpty()) {
   1.510 +    return false;
   1.511 +  }
   1.512 +
   1.513 +  nsCSubstring::const_char_iterator iter, end;
   1.514 +
   1.515 +  aToken.BeginReading(iter);
   1.516 +  aToken.EndReading(end);
   1.517 +
   1.518 +  while (iter != end) {
   1.519 +    if (*iter <= 32 ||
   1.520 +        *iter >= 127 ||
   1.521 +        *iter == '(' ||
   1.522 +        *iter == ')' ||
   1.523 +        *iter == '<' ||
   1.524 +        *iter == '>' ||
   1.525 +        *iter == '@' ||
   1.526 +        *iter == ',' ||
   1.527 +        *iter == ';' ||
   1.528 +        *iter == ':' ||
   1.529 +        *iter == '\\' ||
   1.530 +        *iter == '\"' ||
   1.531 +        *iter == '/' ||
   1.532 +        *iter == '[' ||
   1.533 +        *iter == ']' ||
   1.534 +        *iter == '?' ||
   1.535 +        *iter == '=' ||
   1.536 +        *iter == '{' ||
   1.537 +        *iter == '}') {
   1.538 +      return false;
   1.539 +    }
   1.540 +    ++iter;
   1.541 +  }
   1.542 +
   1.543 +  return true;
   1.544 +}
   1.545 +
   1.546 +nsresult
   1.547 +nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest)
   1.548 +{
   1.549 +  // Check if this was actually a cross domain request
   1.550 +  if (!mHasBeenCrossSite) {
   1.551 +    return NS_OK;
   1.552 +  }
   1.553 +
   1.554 +  if (gDisableCORS) {
   1.555 +    return NS_ERROR_DOM_BAD_URI;
   1.556 +  }
   1.557 +
   1.558 +  // Check if the request failed
   1.559 +  nsresult status;
   1.560 +  nsresult rv = aRequest->GetStatus(&status);
   1.561 +  NS_ENSURE_SUCCESS(rv, rv);
   1.562 +  NS_ENSURE_SUCCESS(status, status);
   1.563 +
   1.564 +  // Test that things worked on a HTTP level
   1.565 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   1.566 +  NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI);
   1.567 +
   1.568 +  // Check the Access-Control-Allow-Origin header
   1.569 +  nsAutoCString allowedOriginHeader;
   1.570 +  rv = http->GetResponseHeader(
   1.571 +    NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader);
   1.572 +  NS_ENSURE_SUCCESS(rv, rv);
   1.573 +
   1.574 +  if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
   1.575 +    nsAutoCString origin;
   1.576 +    rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
   1.577 +    NS_ENSURE_SUCCESS(rv, rv);
   1.578 +
   1.579 +    if (!allowedOriginHeader.Equals(origin)) {
   1.580 +      return NS_ERROR_DOM_BAD_URI;
   1.581 +    }
   1.582 +  }
   1.583 +
   1.584 +  // Check Access-Control-Allow-Credentials header
   1.585 +  if (mWithCredentials) {
   1.586 +    nsAutoCString allowCredentialsHeader;
   1.587 +    rv = http->GetResponseHeader(
   1.588 +      NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader);
   1.589 +    NS_ENSURE_SUCCESS(rv, rv);
   1.590 +
   1.591 +    if (!allowCredentialsHeader.EqualsLiteral("true")) {
   1.592 +      return NS_ERROR_DOM_BAD_URI;
   1.593 +    }
   1.594 +  }
   1.595 +
   1.596 +  if (mIsPreflight) {
   1.597 +    bool succeedded;
   1.598 +    rv = http->GetRequestSucceeded(&succeedded);
   1.599 +    NS_ENSURE_SUCCESS(rv, rv);
   1.600 +    if (!succeedded) {
   1.601 +      return NS_ERROR_DOM_BAD_URI;
   1.602 +    }
   1.603 +
   1.604 +    nsAutoCString headerVal;
   1.605 +    // The "Access-Control-Allow-Methods" header contains a comma separated
   1.606 +    // list of method names.
   1.607 +    http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
   1.608 +                            headerVal);
   1.609 +    bool foundMethod = mPreflightMethod.EqualsLiteral("GET") ||
   1.610 +                         mPreflightMethod.EqualsLiteral("HEAD") ||
   1.611 +                         mPreflightMethod.EqualsLiteral("POST");
   1.612 +    nsCCharSeparatedTokenizer methodTokens(headerVal, ',');
   1.613 +    while(methodTokens.hasMoreTokens()) {
   1.614 +      const nsDependentCSubstring& method = methodTokens.nextToken();
   1.615 +      if (method.IsEmpty()) {
   1.616 +        continue;
   1.617 +      }
   1.618 +      if (!IsValidHTTPToken(method)) {
   1.619 +        return NS_ERROR_DOM_BAD_URI;
   1.620 +      }
   1.621 +      foundMethod |= mPreflightMethod.Equals(method);
   1.622 +    }
   1.623 +    NS_ENSURE_TRUE(foundMethod, NS_ERROR_DOM_BAD_URI);
   1.624 +
   1.625 +    // The "Access-Control-Allow-Headers" header contains a comma separated
   1.626 +    // list of header names.
   1.627 +    headerVal.Truncate();
   1.628 +    http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
   1.629 +                            headerVal);
   1.630 +    nsTArray<nsCString> headers;
   1.631 +    nsCCharSeparatedTokenizer headerTokens(headerVal, ',');
   1.632 +    while(headerTokens.hasMoreTokens()) {
   1.633 +      const nsDependentCSubstring& header = headerTokens.nextToken();
   1.634 +      if (header.IsEmpty()) {
   1.635 +        continue;
   1.636 +      }
   1.637 +      if (!IsValidHTTPToken(header)) {
   1.638 +        return NS_ERROR_DOM_BAD_URI;
   1.639 +      }
   1.640 +      headers.AppendElement(header);
   1.641 +    }
   1.642 +    for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   1.643 +      if (!headers.Contains(mPreflightHeaders[i],
   1.644 +                            nsCaseInsensitiveCStringArrayComparator())) {
   1.645 +        return NS_ERROR_DOM_BAD_URI;
   1.646 +      }
   1.647 +    }
   1.648 +  }
   1.649 +
   1.650 +  return NS_OK;
   1.651 +}
   1.652 +
   1.653 +NS_IMETHODIMP
   1.654 +nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest,
   1.655 +                                   nsISupports* aContext,
   1.656 +                                   nsresult aStatusCode)
   1.657 +{
   1.658 +  nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode);
   1.659 +  mOuterListener = nullptr;
   1.660 +  mOuterNotificationCallbacks = nullptr;
   1.661 +  mRedirectCallback = nullptr;
   1.662 +  mOldRedirectChannel = nullptr;
   1.663 +  mNewRedirectChannel = nullptr;
   1.664 +  return rv;
   1.665 +}
   1.666 +
   1.667 +NS_IMETHODIMP
   1.668 +nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest,
   1.669 +                                     nsISupports* aContext, 
   1.670 +                                     nsIInputStream* aInputStream,
   1.671 +                                     uint64_t aOffset,
   1.672 +                                     uint32_t aCount)
   1.673 +{
   1.674 +  if (!mRequestApproved) {
   1.675 +    return NS_ERROR_DOM_BAD_URI;
   1.676 +  }
   1.677 +  return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream,
   1.678 +                                         aOffset, aCount);
   1.679 +}
   1.680 +
   1.681 +NS_IMETHODIMP
   1.682 +nsCORSListenerProxy::GetInterface(const nsIID & aIID, void **aResult)
   1.683 +{
   1.684 +  if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
   1.685 +    *aResult = static_cast<nsIChannelEventSink*>(this);
   1.686 +    NS_ADDREF_THIS();
   1.687 +
   1.688 +    return NS_OK;
   1.689 +  }
   1.690 +
   1.691 +  return mOuterNotificationCallbacks ?
   1.692 +    mOuterNotificationCallbacks->GetInterface(aIID, aResult) :
   1.693 +    NS_ERROR_NO_INTERFACE;
   1.694 +}
   1.695 +
   1.696 +NS_IMETHODIMP
   1.697 +nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
   1.698 +                                            nsIChannel *aNewChannel,
   1.699 +                                            uint32_t aFlags,
   1.700 +                                            nsIAsyncVerifyRedirectCallback *cb)
   1.701 +{
   1.702 +  nsresult rv;
   1.703 +  if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
   1.704 +    rv = CheckRequestApproved(aOldChannel);
   1.705 +    if (NS_FAILED(rv)) {
   1.706 +      rv = LogBlockedRequest(aOldChannel);
   1.707 +      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
   1.708 +
   1.709 +      if (sPreflightCache) {
   1.710 +        nsCOMPtr<nsIURI> oldURI;
   1.711 +        NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
   1.712 +        if (oldURI) {
   1.713 +          // OK to use mRequestingPrincipal since preflights never get
   1.714 +          // redirected.
   1.715 +          sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal);
   1.716 +        }
   1.717 +      }
   1.718 +      aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
   1.719 +      return NS_ERROR_DOM_BAD_URI;
   1.720 +    }
   1.721 +
   1.722 +    if (mHasBeenCrossSite) {
   1.723 +      // Once we've been cross-site, cross-origin redirects reset our source
   1.724 +      // origin.
   1.725 +      nsCOMPtr<nsIPrincipal> oldChannelPrincipal;
   1.726 +      nsContentUtils::GetSecurityManager()->
   1.727 +        GetChannelPrincipal(aOldChannel, getter_AddRefs(oldChannelPrincipal));
   1.728 +      nsCOMPtr<nsIPrincipal> newChannelPrincipal;
   1.729 +      nsContentUtils::GetSecurityManager()->
   1.730 +        GetChannelPrincipal(aNewChannel, getter_AddRefs(newChannelPrincipal));
   1.731 +      if (!oldChannelPrincipal || !newChannelPrincipal) {
   1.732 +        rv = NS_ERROR_OUT_OF_MEMORY;
   1.733 +      }
   1.734 +
   1.735 +      if (NS_SUCCEEDED(rv)) {
   1.736 +        bool equal;
   1.737 +        rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal);
   1.738 +        if (NS_SUCCEEDED(rv)) {
   1.739 +          if (!equal) {
   1.740 +            // Spec says to set our source origin to a unique origin.
   1.741 +            mOriginHeaderPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
   1.742 +            if (!mOriginHeaderPrincipal) {
   1.743 +              rv = NS_ERROR_OUT_OF_MEMORY;
   1.744 +            }
   1.745 +          }
   1.746 +        }
   1.747 +      }
   1.748 +
   1.749 +      if (NS_FAILED(rv)) {
   1.750 +        aOldChannel->Cancel(rv);
   1.751 +        return rv;
   1.752 +      }
   1.753 +    }
   1.754 +  }
   1.755 +
   1.756 +  // Prepare to receive callback
   1.757 +  mRedirectCallback = cb;
   1.758 +  mOldRedirectChannel = aOldChannel;
   1.759 +  mNewRedirectChannel = aNewChannel;
   1.760 +
   1.761 +  nsCOMPtr<nsIChannelEventSink> outer =
   1.762 +    do_GetInterface(mOuterNotificationCallbacks);
   1.763 +  if (outer) {
   1.764 +    rv = outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, this);
   1.765 +    if (NS_FAILED(rv)) {
   1.766 +        aOldChannel->Cancel(rv); // is this necessary...?
   1.767 +        mRedirectCallback = nullptr;
   1.768 +        mOldRedirectChannel = nullptr;
   1.769 +        mNewRedirectChannel = nullptr;
   1.770 +    }
   1.771 +    return rv;  
   1.772 +  }
   1.773 +
   1.774 +  (void) OnRedirectVerifyCallback(NS_OK);
   1.775 +  return NS_OK;
   1.776 +}
   1.777 +
   1.778 +NS_IMETHODIMP
   1.779 +nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result)
   1.780 +{
   1.781 +  NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   1.782 +  NS_ASSERTION(mOldRedirectChannel, "mOldRedirectChannel not set in callback");
   1.783 +  NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
   1.784 +
   1.785 +  if (NS_SUCCEEDED(result)) {
   1.786 +      nsresult rv = UpdateChannel(mNewRedirectChannel);
   1.787 +      if (NS_FAILED(rv)) {
   1.788 +          NS_WARNING("nsCORSListenerProxy::OnRedirectVerifyCallback: "
   1.789 +                     "UpdateChannel() returned failure");
   1.790 +      }
   1.791 +      result = rv;
   1.792 +  }
   1.793 +
   1.794 +  if (NS_FAILED(result)) {
   1.795 +    mOldRedirectChannel->Cancel(result);
   1.796 +  }
   1.797 +
   1.798 +  mOldRedirectChannel = nullptr;
   1.799 +  mNewRedirectChannel = nullptr;
   1.800 +  mRedirectCallback->OnRedirectVerifyCallback(result);
   1.801 +  mRedirectCallback   = nullptr;
   1.802 +  return NS_OK;
   1.803 +}
   1.804 +
   1.805 +nsresult
   1.806 +nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI)
   1.807 +{
   1.808 +  nsCOMPtr<nsIURI> uri, originalURI;
   1.809 +  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   1.810 +  NS_ENSURE_SUCCESS(rv, rv);
   1.811 +  rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
   1.812 +  NS_ENSURE_SUCCESS(rv, rv);
   1.813 +
   1.814 +  // exempt data URIs from the same origin check.
   1.815 +  if (aAllowDataURI && originalURI == uri) {
   1.816 +    bool dataScheme = false;
   1.817 +    rv = uri->SchemeIs("data", &dataScheme);
   1.818 +    NS_ENSURE_SUCCESS(rv, rv);
   1.819 +    if (dataScheme) {
   1.820 +      return NS_OK;
   1.821 +    }
   1.822 +  }
   1.823 +
   1.824 +  // Check that the uri is ok to load
   1.825 +  rv = nsContentUtils::GetSecurityManager()->
   1.826 +    CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
   1.827 +                              nsIScriptSecurityManager::STANDARD);
   1.828 +  NS_ENSURE_SUCCESS(rv, rv);
   1.829 +
   1.830 +  if (originalURI != uri) {
   1.831 +    rv = nsContentUtils::GetSecurityManager()->
   1.832 +      CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI,
   1.833 +                                nsIScriptSecurityManager::STANDARD);
   1.834 +    NS_ENSURE_SUCCESS(rv, rv);
   1.835 +  }
   1.836 +
   1.837 +  if (!mHasBeenCrossSite &&
   1.838 +      NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) &&
   1.839 +      (originalURI == uri ||
   1.840 +       NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI,
   1.841 +                                                       false, false)))) {
   1.842 +    return NS_OK;
   1.843 +  }
   1.844 +
   1.845 +  // It's a cross site load
   1.846 +  mHasBeenCrossSite = true;
   1.847 +
   1.848 +  nsCString userpass;
   1.849 +  uri->GetUserPass(userpass);
   1.850 +  NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI);
   1.851 +
   1.852 +  // Add the Origin header
   1.853 +  nsAutoCString origin;
   1.854 +  rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin);
   1.855 +  NS_ENSURE_SUCCESS(rv, rv);
   1.856 +
   1.857 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
   1.858 +  NS_ENSURE_TRUE(http, NS_ERROR_FAILURE);
   1.859 +
   1.860 +  rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false);
   1.861 +  NS_ENSURE_SUCCESS(rv, rv);
   1.862 +
   1.863 +  // Add preflight headers if this is a preflight request
   1.864 +  if (mIsPreflight) {
   1.865 +    rv = http->
   1.866 +      SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
   1.867 +                       mPreflightMethod, false);
   1.868 +    NS_ENSURE_SUCCESS(rv, rv);
   1.869 +
   1.870 +    if (!mPreflightHeaders.IsEmpty()) {
   1.871 +      nsAutoCString headers;
   1.872 +      for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
   1.873 +        if (i != 0) {
   1.874 +          headers += ',';
   1.875 +        }
   1.876 +        headers += mPreflightHeaders[i];
   1.877 +      }
   1.878 +      rv = http->
   1.879 +        SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
   1.880 +                         headers, false);
   1.881 +      NS_ENSURE_SUCCESS(rv, rv);
   1.882 +    }
   1.883 +  }
   1.884 +
   1.885 +  // Make cookie-less if needed
   1.886 +  if (mIsPreflight || !mWithCredentials) {
   1.887 +    nsLoadFlags flags;
   1.888 +    rv = http->GetLoadFlags(&flags);
   1.889 +    NS_ENSURE_SUCCESS(rv, rv);
   1.890 +
   1.891 +    flags |= nsIRequest::LOAD_ANONYMOUS;
   1.892 +    rv = http->SetLoadFlags(flags);
   1.893 +    NS_ENSURE_SUCCESS(rv, rv);
   1.894 +  }
   1.895 +
   1.896 +  return NS_OK;
   1.897 +}
   1.898 +
   1.899 +//////////////////////////////////////////////////////////////////////////
   1.900 +// Preflight proxy
   1.901 +
   1.902 +// Class used as streamlistener and notification callback when
   1.903 +// doing the initial OPTIONS request for a CORS check
   1.904 +class nsCORSPreflightListener MOZ_FINAL : public nsIStreamListener,
   1.905 +                                          public nsIInterfaceRequestor,
   1.906 +                                          public nsIChannelEventSink
   1.907 +{
   1.908 +public:
   1.909 +  nsCORSPreflightListener(nsIChannel* aOuterChannel,
   1.910 +                          nsIStreamListener* aOuterListener,
   1.911 +                          nsISupports* aOuterContext,
   1.912 +                          nsIPrincipal* aReferrerPrincipal,
   1.913 +                          const nsACString& aRequestMethod,
   1.914 +                          bool aWithCredentials)
   1.915 +   : mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
   1.916 +     mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
   1.917 +     mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials)
   1.918 +  { }
   1.919 +
   1.920 +  NS_DECL_ISUPPORTS
   1.921 +  NS_DECL_NSISTREAMLISTENER
   1.922 +  NS_DECL_NSIREQUESTOBSERVER
   1.923 +  NS_DECL_NSIINTERFACEREQUESTOR
   1.924 +  NS_DECL_NSICHANNELEVENTSINK
   1.925 +
   1.926 +private:
   1.927 +  void AddResultToCache(nsIRequest* aRequest);
   1.928 +
   1.929 +  nsCOMPtr<nsIChannel> mOuterChannel;
   1.930 +  nsCOMPtr<nsIStreamListener> mOuterListener;
   1.931 +  nsCOMPtr<nsISupports> mOuterContext;
   1.932 +  nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
   1.933 +  nsCString mRequestMethod;
   1.934 +  bool mWithCredentials;
   1.935 +};
   1.936 +
   1.937 +NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener,
   1.938 +                  nsIRequestObserver, nsIInterfaceRequestor,
   1.939 +                  nsIChannelEventSink)
   1.940 +
   1.941 +void
   1.942 +nsCORSPreflightListener::AddResultToCache(nsIRequest *aRequest)
   1.943 +{
   1.944 +  nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
   1.945 +  NS_ASSERTION(http, "Request was not http");
   1.946 +
   1.947 +  // The "Access-Control-Max-Age" header should return an age in seconds.
   1.948 +  nsAutoCString headerVal;
   1.949 +  http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
   1.950 +                          headerVal);
   1.951 +  if (headerVal.IsEmpty()) {
   1.952 +    return;
   1.953 +  }
   1.954 +
   1.955 +  // Sanitize the string. We only allow 'delta-seconds' as specified by
   1.956 +  // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
   1.957 +  // trailing non-whitespace characters).
   1.958 +  uint32_t age = 0;
   1.959 +  nsCSubstring::const_char_iterator iter, end;
   1.960 +  headerVal.BeginReading(iter);
   1.961 +  headerVal.EndReading(end);
   1.962 +  while (iter != end) {
   1.963 +    if (*iter < '0' || *iter > '9') {
   1.964 +      return;
   1.965 +    }
   1.966 +    age = age * 10 + (*iter - '0');
   1.967 +    // Cap at 24 hours. This also avoids overflow
   1.968 +    age = std::min(age, 86400U);
   1.969 +    ++iter;
   1.970 +  }
   1.971 +
   1.972 +  if (!age || !EnsurePreflightCache()) {
   1.973 +    return;
   1.974 +  }
   1.975 +
   1.976 +
   1.977 +  // String seems fine, go ahead and cache.
   1.978 +  // Note that we have already checked that these headers follow the correct
   1.979 +  // syntax.
   1.980 +
   1.981 +  nsCOMPtr<nsIURI> uri;
   1.982 +  NS_GetFinalChannelURI(http, getter_AddRefs(uri));
   1.983 +
   1.984 +  TimeStamp expirationTime = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
   1.985 +
   1.986 +  nsPreflightCache::CacheEntry* entry =
   1.987 +    sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials,
   1.988 +                              true);
   1.989 +  if (!entry) {
   1.990 +    return;
   1.991 +  }
   1.992 +
   1.993 +  // The "Access-Control-Allow-Methods" header contains a comma separated
   1.994 +  // list of method names.
   1.995 +  http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
   1.996 +                          headerVal);
   1.997 +
   1.998 +  nsCCharSeparatedTokenizer methods(headerVal, ',');
   1.999 +  while(methods.hasMoreTokens()) {
  1.1000 +    const nsDependentCSubstring& method = methods.nextToken();
  1.1001 +    if (method.IsEmpty()) {
  1.1002 +      continue;
  1.1003 +    }
  1.1004 +    uint32_t i;
  1.1005 +    for (i = 0; i < entry->mMethods.Length(); ++i) {
  1.1006 +      if (entry->mMethods[i].token.Equals(method)) {
  1.1007 +        entry->mMethods[i].expirationTime = expirationTime;
  1.1008 +        break;
  1.1009 +      }
  1.1010 +    }
  1.1011 +    if (i == entry->mMethods.Length()) {
  1.1012 +      nsPreflightCache::TokenTime* newMethod =
  1.1013 +        entry->mMethods.AppendElement();
  1.1014 +      if (!newMethod) {
  1.1015 +        return;
  1.1016 +      }
  1.1017 +
  1.1018 +      newMethod->token = method;
  1.1019 +      newMethod->expirationTime = expirationTime;
  1.1020 +    }
  1.1021 +  }
  1.1022 +
  1.1023 +  // The "Access-Control-Allow-Headers" header contains a comma separated
  1.1024 +  // list of method names.
  1.1025 +  http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
  1.1026 +                          headerVal);
  1.1027 +
  1.1028 +  nsCCharSeparatedTokenizer headers(headerVal, ',');
  1.1029 +  while(headers.hasMoreTokens()) {
  1.1030 +    const nsDependentCSubstring& header = headers.nextToken();
  1.1031 +    if (header.IsEmpty()) {
  1.1032 +      continue;
  1.1033 +    }
  1.1034 +    uint32_t i;
  1.1035 +    for (i = 0; i < entry->mHeaders.Length(); ++i) {
  1.1036 +      if (entry->mHeaders[i].token.Equals(header)) {
  1.1037 +        entry->mHeaders[i].expirationTime = expirationTime;
  1.1038 +        break;
  1.1039 +      }
  1.1040 +    }
  1.1041 +    if (i == entry->mHeaders.Length()) {
  1.1042 +      nsPreflightCache::TokenTime* newHeader =
  1.1043 +        entry->mHeaders.AppendElement();
  1.1044 +      if (!newHeader) {
  1.1045 +        return;
  1.1046 +      }
  1.1047 +
  1.1048 +      newHeader->token = header;
  1.1049 +      newHeader->expirationTime = expirationTime;
  1.1050 +    }
  1.1051 +  }
  1.1052 +}
  1.1053 +
  1.1054 +NS_IMETHODIMP
  1.1055 +nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest,
  1.1056 +                                        nsISupports *aContext)
  1.1057 +{
  1.1058 +  nsresult status;
  1.1059 +  nsresult rv = aRequest->GetStatus(&status);
  1.1060 +
  1.1061 +  if (NS_SUCCEEDED(rv)) {
  1.1062 +    rv = status;
  1.1063 +  }
  1.1064 +
  1.1065 +  if (NS_SUCCEEDED(rv)) {
  1.1066 +    // Everything worked, try to cache and then fire off the actual request.
  1.1067 +    AddResultToCache(aRequest);
  1.1068 +
  1.1069 +    rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
  1.1070 +  }
  1.1071 +
  1.1072 +  if (NS_FAILED(rv)) {
  1.1073 +    mOuterChannel->Cancel(rv);
  1.1074 +    mOuterListener->OnStartRequest(mOuterChannel, mOuterContext);
  1.1075 +    mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv);
  1.1076 +    
  1.1077 +    return rv;
  1.1078 +  }
  1.1079 +
  1.1080 +  return NS_OK;
  1.1081 +}
  1.1082 +
  1.1083 +NS_IMETHODIMP
  1.1084 +nsCORSPreflightListener::OnStopRequest(nsIRequest *aRequest,
  1.1085 +                                       nsISupports *aContext,
  1.1086 +                                       nsresult aStatus)
  1.1087 +{
  1.1088 +  mOuterChannel = nullptr;
  1.1089 +  mOuterListener = nullptr;
  1.1090 +  mOuterContext = nullptr;
  1.1091 +  return NS_OK;
  1.1092 +}
  1.1093 +
  1.1094 +/** nsIStreamListener methods **/
  1.1095 +
  1.1096 +NS_IMETHODIMP
  1.1097 +nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest,
  1.1098 +                                         nsISupports *ctxt,
  1.1099 +                                         nsIInputStream *inStr,
  1.1100 +                                         uint64_t sourceOffset,
  1.1101 +                                         uint32_t count)
  1.1102 +{
  1.1103 +  uint32_t totalRead;
  1.1104 +  return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
  1.1105 +}
  1.1106 +
  1.1107 +NS_IMETHODIMP
  1.1108 +nsCORSPreflightListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
  1.1109 +                                                nsIChannel *aNewChannel,
  1.1110 +                                                uint32_t aFlags,
  1.1111 +                                                nsIAsyncVerifyRedirectCallback *callback)
  1.1112 +{
  1.1113 +  // Only internal redirects allowed for now.
  1.1114 +  if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags))
  1.1115 +    return NS_ERROR_DOM_BAD_URI;
  1.1116 +
  1.1117 +  callback->OnRedirectVerifyCallback(NS_OK);
  1.1118 +  return NS_OK;
  1.1119 +}
  1.1120 +
  1.1121 +NS_IMETHODIMP
  1.1122 +nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult)
  1.1123 +{
  1.1124 +  return QueryInterface(aIID, aResult);
  1.1125 +}
  1.1126 +
  1.1127 +
  1.1128 +nsresult
  1.1129 +NS_StartCORSPreflight(nsIChannel* aRequestChannel,
  1.1130 +                      nsIStreamListener* aListener,
  1.1131 +                      nsIPrincipal* aPrincipal,
  1.1132 +                      bool aWithCredentials,
  1.1133 +                      nsTArray<nsCString>& aUnsafeHeaders,
  1.1134 +                      nsIChannel** aPreflightChannel)
  1.1135 +{
  1.1136 +  *aPreflightChannel = nullptr;
  1.1137 +
  1.1138 +  nsAutoCString method;
  1.1139 +  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
  1.1140 +  NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
  1.1141 +  httpChannel->GetRequestMethod(method);
  1.1142 +
  1.1143 +  nsCOMPtr<nsIURI> uri;
  1.1144 +  nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
  1.1145 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1146 +
  1.1147 +  nsPreflightCache::CacheEntry* entry =
  1.1148 +    sPreflightCache ?
  1.1149 +    sPreflightCache->GetEntry(uri, aPrincipal, aWithCredentials, false) :
  1.1150 +    nullptr;
  1.1151 +
  1.1152 +  if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
  1.1153 +    // We have a cached preflight result, just start the original channel
  1.1154 +    return aRequestChannel->AsyncOpen(aListener, nullptr);
  1.1155 +  }
  1.1156 +
  1.1157 +  // Either it wasn't cached or the cached result has expired. Build a
  1.1158 +  // channel for the OPTIONS request.
  1.1159 +
  1.1160 +  nsCOMPtr<nsILoadGroup> loadGroup;
  1.1161 +  rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  1.1162 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1163 +
  1.1164 +  nsLoadFlags loadFlags;
  1.1165 +  rv = aRequestChannel->GetLoadFlags(&loadFlags);
  1.1166 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1167 +
  1.1168 +  nsCOMPtr<nsIChannel> preflightChannel;
  1.1169 +  rv = NS_NewChannel(getter_AddRefs(preflightChannel), uri, nullptr,
  1.1170 +                     loadGroup, nullptr, loadFlags);
  1.1171 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1172 +
  1.1173 +  nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel);
  1.1174 +  NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!");
  1.1175 +
  1.1176 +  rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
  1.1177 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1178 +  
  1.1179 +  // Set up listener which will start the original channel
  1.1180 +  nsCOMPtr<nsIStreamListener> preflightListener =
  1.1181 +    new nsCORSPreflightListener(aRequestChannel, aListener, nullptr, aPrincipal,
  1.1182 +                                method, aWithCredentials);
  1.1183 +  NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY);
  1.1184 +
  1.1185 +  nsRefPtr<nsCORSListenerProxy> corsListener =
  1.1186 +    new nsCORSListenerProxy(preflightListener, aPrincipal,
  1.1187 +                            aWithCredentials, method,
  1.1188 +                            aUnsafeHeaders);
  1.1189 +  rv = corsListener->Init(preflightChannel);
  1.1190 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1191 +  preflightListener = corsListener;
  1.1192 +
  1.1193 +  // Start preflight
  1.1194 +  rv = preflightChannel->AsyncOpen(preflightListener, nullptr);
  1.1195 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1196 +  
  1.1197 +  // Return newly created preflight channel
  1.1198 +  preflightChannel.forget(aPreflightChannel);
  1.1199 +
  1.1200 +  return NS_OK;
  1.1201 +}
  1.1202 +

mercurial