content/base/src/nsCrossSiteListenerProxy.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

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

mercurial