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