1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 the Mozilla browser.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications, Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 1999
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Travis Bogard <travis@netscape.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsDocShell.h"
40 : #include "nsDSURIContentListener.h"
41 : #include "nsIChannel.h"
42 : #include "nsServiceManagerUtils.h"
43 : #include "nsXPIDLString.h"
44 : #include "nsDocShellCID.h"
45 : #include "nsIWebNavigationInfo.h"
46 : #include "nsIDOMWindow.h"
47 : #include "nsAutoPtr.h"
48 : #include "nsIHttpChannel.h"
49 : #include "nsIScriptSecurityManager.h"
50 : #include "nsNetError.h"
51 : #include "mozilla/Preferences.h"
52 :
53 : using namespace mozilla;
54 :
55 : static bool sIgnoreXFrameOptions = false;
56 :
57 : //*****************************************************************************
58 : //*** nsDSURIContentListener: Object Management
59 : //*****************************************************************************
60 :
61 0 : nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
62 : : mDocShell(aDocShell),
63 0 : mParentContentListener(nsnull)
64 : {
65 : static bool initializedPrefCache = false;
66 :
67 : // Set up a pref cache for sIgnoreXFrameOptions, if we haven't already.
68 0 : if (NS_UNLIKELY(!initializedPrefCache)) {
69 : // Lock the pref so that the user's changes to it, if any, are ignored.
70 0 : nsIPrefBranch *root = Preferences::GetRootBranch();
71 0 : root->LockPref("b2g.ignoreXFrameOptions");
72 :
73 0 : Preferences::AddBoolVarCache(&sIgnoreXFrameOptions, "b2g.ignoreXFrameOptions");
74 0 : initializedPrefCache = true;
75 : }
76 0 : }
77 :
78 0 : nsDSURIContentListener::~nsDSURIContentListener()
79 : {
80 0 : }
81 :
82 : nsresult
83 0 : nsDSURIContentListener::Init()
84 : {
85 : nsresult rv;
86 0 : mNavInfo = do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv);
87 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get webnav info");
88 0 : return rv;
89 : }
90 :
91 :
92 : //*****************************************************************************
93 : // nsDSURIContentListener::nsISupports
94 : //*****************************************************************************
95 :
96 0 : NS_IMPL_THREADSAFE_ADDREF(nsDSURIContentListener)
97 0 : NS_IMPL_THREADSAFE_RELEASE(nsDSURIContentListener)
98 :
99 0 : NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener)
100 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener)
101 0 : NS_INTERFACE_MAP_ENTRY(nsIURIContentListener)
102 0 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
103 0 : NS_INTERFACE_MAP_END
104 :
105 : //*****************************************************************************
106 : // nsDSURIContentListener::nsIURIContentListener
107 : //*****************************************************************************
108 :
109 : NS_IMETHODIMP
110 0 : nsDSURIContentListener::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen)
111 : {
112 : // If mDocShell is null here, that means someone's starting a load
113 : // in our docshell after it's already been destroyed. Don't let
114 : // that happen.
115 0 : if (!mDocShell) {
116 0 : *aAbortOpen = true;
117 0 : return NS_OK;
118 : }
119 :
120 0 : nsCOMPtr<nsIURIContentListener> parentListener;
121 0 : GetParentContentListener(getter_AddRefs(parentListener));
122 0 : if (parentListener)
123 0 : return parentListener->OnStartURIOpen(aURI, aAbortOpen);
124 :
125 0 : return NS_OK;
126 : }
127 :
128 : NS_IMETHODIMP
129 0 : nsDSURIContentListener::DoContent(const char* aContentType,
130 : bool aIsContentPreferred,
131 : nsIRequest* request,
132 : nsIStreamListener** aContentHandler,
133 : bool* aAbortProcess)
134 : {
135 : nsresult rv;
136 0 : NS_ENSURE_ARG_POINTER(aContentHandler);
137 0 : NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
138 :
139 : // Check whether X-Frame-Options permits us to load this content in an
140 : // iframe and abort the load (unless we've disabled x-frame-options
141 : // checking).
142 0 : if (!CheckFrameOptions(request)) {
143 0 : *aAbortProcess = true;
144 0 : return NS_OK;
145 : }
146 :
147 0 : *aAbortProcess = false;
148 :
149 : // determine if the channel has just been retargeted to us...
150 0 : nsLoadFlags loadFlags = 0;
151 0 : nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(request);
152 :
153 0 : if (aOpenedChannel)
154 0 : aOpenedChannel->GetLoadFlags(&loadFlags);
155 :
156 0 : if(loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
157 : {
158 : // XXX: Why does this not stop the content too?
159 0 : mDocShell->Stop(nsIWebNavigation::STOP_NETWORK);
160 :
161 0 : mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL);
162 : }
163 :
164 0 : rv = mDocShell->CreateContentViewer(aContentType, request, aContentHandler);
165 :
166 0 : if (rv == NS_ERROR_REMOTE_XUL) {
167 0 : request->Cancel(rv);
168 0 : return NS_OK;
169 : }
170 :
171 0 : if (NS_FAILED(rv)) {
172 : // it's okay if we don't know how to handle the content
173 0 : return NS_OK;
174 : }
175 :
176 0 : if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
177 0 : nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell));
178 0 : NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
179 0 : domWindow->Focus();
180 : }
181 :
182 0 : return NS_OK;
183 : }
184 :
185 : NS_IMETHODIMP
186 0 : nsDSURIContentListener::IsPreferred(const char* aContentType,
187 : char ** aDesiredContentType,
188 : bool* aCanHandle)
189 : {
190 0 : NS_ENSURE_ARG_POINTER(aCanHandle);
191 0 : NS_ENSURE_ARG_POINTER(aDesiredContentType);
192 :
193 : // the docshell has no idea if it is the preferred content provider or not.
194 : // It needs to ask its parent if it is the preferred content handler or not...
195 :
196 0 : nsCOMPtr<nsIURIContentListener> parentListener;
197 0 : GetParentContentListener(getter_AddRefs(parentListener));
198 0 : if (parentListener) {
199 0 : return parentListener->IsPreferred(aContentType,
200 : aDesiredContentType,
201 0 : aCanHandle);
202 : }
203 : // we used to return false here if we didn't have a parent properly
204 : // registered at the top of the docshell hierarchy to dictate what
205 : // content types this docshell should be a preferred handler for. But
206 : // this really makes it hard for developers using iframe or browser tags
207 : // because then they need to make sure they implement
208 : // nsIURIContentListener otherwise all link clicks would get sent to
209 : // another window because we said we weren't the preferred handler type.
210 : // I'm going to change the default now...if we can handle the content,
211 : // and someone didn't EXPLICITLY set a nsIURIContentListener at the top
212 : // of our docshell chain, then we'll now always attempt to process the
213 : // content ourselves...
214 : return CanHandleContent(aContentType,
215 : true,
216 : aDesiredContentType,
217 0 : aCanHandle);
218 : }
219 :
220 : NS_IMETHODIMP
221 0 : nsDSURIContentListener::CanHandleContent(const char* aContentType,
222 : bool aIsContentPreferred,
223 : char ** aDesiredContentType,
224 : bool* aCanHandleContent)
225 : {
226 0 : NS_PRECONDITION(aCanHandleContent, "Null out param?");
227 0 : NS_ENSURE_ARG_POINTER(aDesiredContentType);
228 :
229 0 : *aCanHandleContent = false;
230 0 : *aDesiredContentType = nsnull;
231 :
232 0 : nsresult rv = NS_OK;
233 0 : if (aContentType) {
234 0 : PRUint32 canHandle = nsIWebNavigationInfo::UNSUPPORTED;
235 0 : rv = mNavInfo->IsTypeSupported(nsDependentCString(aContentType),
236 : mDocShell,
237 0 : &canHandle);
238 0 : *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED);
239 : }
240 :
241 0 : return rv;
242 : }
243 :
244 : NS_IMETHODIMP
245 0 : nsDSURIContentListener::GetLoadCookie(nsISupports ** aLoadCookie)
246 : {
247 0 : NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell));
248 0 : return NS_OK;
249 : }
250 :
251 : NS_IMETHODIMP
252 0 : nsDSURIContentListener::SetLoadCookie(nsISupports * aLoadCookie)
253 : {
254 : #ifdef DEBUG
255 : nsRefPtr<nsDocLoader> cookieAsDocLoader =
256 0 : nsDocLoader::GetAsDocLoader(aLoadCookie);
257 0 : NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell,
258 : "Invalid load cookie being set!");
259 : #endif
260 0 : return NS_OK;
261 : }
262 :
263 : NS_IMETHODIMP
264 0 : nsDSURIContentListener::GetParentContentListener(nsIURIContentListener**
265 : aParentListener)
266 : {
267 0 : if (mWeakParentContentListener)
268 : {
269 : nsCOMPtr<nsIURIContentListener> tempListener =
270 0 : do_QueryReferent(mWeakParentContentListener);
271 0 : *aParentListener = tempListener;
272 0 : NS_IF_ADDREF(*aParentListener);
273 : }
274 : else {
275 0 : *aParentListener = mParentContentListener;
276 0 : NS_IF_ADDREF(*aParentListener);
277 : }
278 0 : return NS_OK;
279 : }
280 :
281 : NS_IMETHODIMP
282 0 : nsDSURIContentListener::SetParentContentListener(nsIURIContentListener*
283 : aParentListener)
284 : {
285 0 : if (aParentListener)
286 : {
287 : // Store the parent listener as a weak ref. Parents not supporting
288 : // nsISupportsWeakReference assert but may still be used.
289 0 : mParentContentListener = nsnull;
290 0 : mWeakParentContentListener = do_GetWeakReference(aParentListener);
291 0 : if (!mWeakParentContentListener)
292 : {
293 0 : mParentContentListener = aParentListener;
294 : }
295 : }
296 : else
297 : {
298 0 : mWeakParentContentListener = nsnull;
299 0 : mParentContentListener = nsnull;
300 : }
301 0 : return NS_OK;
302 : }
303 :
304 : // Check if X-Frame-Options permits this document to be loaded as a subdocument.
305 0 : bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
306 : {
307 : // If X-Frame-Options checking is disabled, return true unconditionally.
308 0 : if (sIgnoreXFrameOptions) {
309 0 : return true;
310 : }
311 :
312 0 : nsCAutoString xfoHeaderValue;
313 :
314 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
315 0 : if (!httpChannel) {
316 0 : return true;
317 : }
318 :
319 0 : httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
320 0 : xfoHeaderValue);
321 :
322 : // return early if header does not have one of the two values with meaning
323 0 : if (!xfoHeaderValue.LowerCaseEqualsLiteral("deny") &&
324 0 : !xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin"))
325 0 : return true;
326 :
327 0 : if (mDocShell) {
328 : // We need to check the location of this window and the location of the top
329 : // window, if we're not the top. X-F-O: SAMEORIGIN requires that the
330 : // document must be same-origin with top window. X-F-O: DENY requires that
331 : // the document must never be framed.
332 0 : nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell));
333 : // If we don't have DOMWindow there is no risk of clickjacking
334 0 : if (!thisWindow)
335 0 : return true;
336 :
337 0 : nsCOMPtr<nsIDOMWindow> topWindow;
338 0 : thisWindow->GetTop(getter_AddRefs(topWindow));
339 :
340 : // if the document is in the top window, it's not in a frame.
341 0 : if (thisWindow == topWindow)
342 0 : return true;
343 :
344 : // Find the top docshell in our parent chain that doesn't have the system
345 : // principal and use it for the principal comparison. Finding the top
346 : // content-type docshell doesn't work because some chrome documents are
347 : // loaded in content docshells (see bug 593387).
348 : nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface(
349 0 : static_cast<nsIDocShell*> (mDocShell)));
350 0 : nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem,
351 0 : curDocShellItem = thisDocShellItem;
352 0 : nsCOMPtr<nsIDocument> topDoc;
353 : nsresult rv;
354 : nsCOMPtr<nsIScriptSecurityManager> ssm =
355 0 : do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
356 0 : if (!ssm)
357 0 : return false;
358 :
359 : // Traverse up the parent chain to the top docshell that doesn't have
360 : // a system principal
361 0 : while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
362 0 : parentDocShellItem) {
363 0 : bool system = false;
364 0 : topDoc = do_GetInterface(parentDocShellItem);
365 0 : if (topDoc) {
366 0 : if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
367 : &system)) && system) {
368 0 : break;
369 : }
370 : }
371 : else {
372 0 : return false;
373 : }
374 0 : curDocShellItem = parentDocShellItem;
375 : }
376 :
377 : // If this document has the top non-SystemPrincipal docshell it is not being
378 : // framed or it is being framed by a chrome document, which we allow.
379 0 : if (curDocShellItem == thisDocShellItem)
380 0 : return true;
381 :
382 : // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
383 : // parent chain must be from the same origin as this document.
384 0 : if (xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin")) {
385 0 : nsCOMPtr<nsIURI> uri;
386 0 : httpChannel->GetURI(getter_AddRefs(uri));
387 0 : topDoc = do_GetInterface(curDocShellItem);
388 0 : nsCOMPtr<nsIURI> topUri;
389 0 : topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
390 0 : rv = ssm->CheckSameOriginURI(uri, topUri, true);
391 0 : if (NS_SUCCEEDED(rv))
392 0 : return true;
393 : }
394 :
395 : else {
396 : // If the value of the header is DENY, then the document
397 : // should never be permitted to load as a subdocument.
398 0 : NS_ASSERTION(xfoHeaderValue.LowerCaseEqualsLiteral("deny"),
399 : "How did we get here with some random header value?");
400 : }
401 :
402 : // cancel the load and display about:blank
403 0 : httpChannel->Cancel(NS_BINDING_ABORTED);
404 0 : nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(mDocShell));
405 0 : if (webNav) {
406 0 : webNav->LoadURI(NS_LITERAL_STRING("about:blank").get(),
407 0 : 0, nsnull, nsnull, nsnull);
408 : }
409 0 : return false;
410 : }
411 :
412 0 : return true;
413 : }
|