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