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