Back to index

lightning-sunbird  0.9+nobinonly
nsFrameLoader.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Johnny Stenback <jst@netscape.com> (original author)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsIDOMHTMLIFrameElement.h"
00040 #include "nsIDOMHTMLFrameElement.h"
00041 #include "nsIDOMWindow.h"
00042 #include "nsPresContext.h"
00043 #include "nsIPresShell.h"
00044 #include "nsIContent.h"
00045 #include "nsIDocument.h"
00046 #include "nsIDOMDocument.h"
00047 #include "nsIDOMWindow.h"
00048 #include "nsPIDOMWindow.h"
00049 #include "nsIWebNavigation.h"
00050 #include "nsIChromeEventHandler.h"
00051 #include "nsIDocShell.h"
00052 #include "nsIDocShellTreeItem.h"
00053 #include "nsIDocShellTreeNode.h"
00054 #include "nsIDocShellTreeOwner.h"
00055 #include "nsIDocShellLoadInfo.h"
00056 #include "nsIBaseWindow.h"
00057 #include "nsContentUtils.h"
00058 #include "nsUnicharUtils.h"
00059 #include "nsIScriptGlobalObject.h"
00060 #include "nsIScriptSecurityManager.h"
00061 #include "nsFrameLoader.h"
00062 
00063 #include "nsIURI.h"
00064 #include "nsIURL.h"
00065 #include "nsNetUtil.h"
00066 
00067 #include "nsHTMLAtoms.h"
00068 #include "nsINameSpaceManager.h"
00069 
00070 // Bug 136580: Limit to the number of nested content frames that can have the
00071 //             same URL. This is to stop content that is recursively loading
00072 //             itself.  Note that "#foo" on the end of URL doesn't affect
00073 //             whether it's considered identical, but "?foo" or ";foo" are
00074 //             considered and compared.
00075 // Bug 228829: Limit this to 1, like IE does.
00076 #define MAX_SAME_URL_CONTENT_FRAMES 1
00077 
00078 // Bug 8065: Limit content frame depth to some reasonable level. This
00079 // does not count chrome frames when determining depth, nor does it
00080 // prevent chrome recursion.  Number is fairly arbitrary, but meant to
00081 // keep number of shells to a reasonable number on accidental recursion with a
00082 // small (but not 1) branching factor.  With large branching factors the number
00083 // of shells can rapidly become huge and run us out of memory.  To solve that,
00084 // we'd need to re-institute a fixed version of bug 98158.
00085 #define MAX_DEPTH_CONTENT_FRAMES 10
00086 
00087 NS_IMPL_ISUPPORTS1(nsFrameLoader, nsIFrameLoader)
00088 
00089 NS_IMETHODIMP
00090 nsFrameLoader::LoadFrame()
00091 {
00092   NS_ENSURE_TRUE(mOwnerContent, NS_ERROR_NOT_INITIALIZED);
00093 
00094   nsresult rv = EnsureDocShell();
00095   NS_ENSURE_SUCCESS(rv, rv);
00096 
00097   nsIDocument* doc = mOwnerContent->GetDocument();
00098   if (!doc) {
00099     return NS_OK;
00100   }
00101 
00102   nsAutoString src;
00103   GetURL(src);
00104 
00105   src.Trim(" \t\n\r");
00106 
00107   if (src.IsEmpty()) {
00108     src.AssignLiteral("about:blank");
00109   }
00110 
00111   nsCOMPtr<nsIURI> base_uri = mOwnerContent->GetBaseURI();
00112   const nsAFlatCString &doc_charset = doc->GetDocumentCharacterSet();
00113 
00114   nsCOMPtr<nsIURI> uri;
00115   rv = NS_NewURI(getter_AddRefs(uri), src,
00116                  doc_charset.IsEmpty() ? nsnull : doc_charset.get(),
00117                  base_uri);
00118   NS_ENSURE_SUCCESS(rv, rv);
00119 
00120   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
00121   mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo));
00122   NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
00123 
00124   // Check for security.  The fun part is trying to figure out what principals
00125   // to use.  The way I figure it, if we're doing a LoadFrame() accidentally
00126   // (eg someone created a frame/iframe node, we're being parsed, XUL iframes
00127   // are being reframed, etc.) then we definitely want to use the node
00128   // principal of mOwnerContent for security checks.  If, on the other hand,
00129   // someone's setting the src on our owner content, or created it via script,
00130   // or whatever, then they can clearly access it... and we should still use
00131   // the principal of mOwnerContent.  I don't think that leads to privilege
00132   // escalation, and it's reasonably guaranteed to not lead to XSS issues
00133   // (since caller can already access mOwnerContent in this case.  So just use
00134   // the principal of mOwnerContent no matter what.  If script wants to run
00135   // things with its own permissions, which differ from those of mOwnerContent
00136   // (which means the script is privileged in some way) it should set
00137   // window.location instead.
00138   nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
00139 
00140   // Get our principal
00141   nsIPrincipal* principal = doc->GetPrincipal();
00142 
00143   if (!principal) {
00144     return NS_ERROR_FAILURE;
00145   }
00146   
00147   // Check if we are allowed to load absURL
00148   rv = secMan->CheckLoadURIWithPrincipal(principal, uri,
00149                                          nsIScriptSecurityManager::STANDARD);
00150   if (NS_FAILED(rv)) {
00151     return rv; // We're not
00152   }
00153 
00154   // Bail out if this is an infinite recursion scenario
00155   rv = CheckForRecursiveLoad(uri);
00156   NS_ENSURE_SUCCESS(rv, rv);
00157   
00158   // Is our principal the system principal?
00159   nsCOMPtr<nsIPrincipal> sysPrin;
00160   rv = secMan->GetSystemPrincipal(getter_AddRefs(sysPrin));
00161   NS_ENSURE_SUCCESS(rv, rv);
00162 
00163   // We'll use our principal, not that of the document loaded inside us.
00164   // This is very important; needed to prevent XSS attacks on documents
00165   // loaded in subframes!  Note that if |principal == sysPrin| the
00166   // situation is handled by nsDocShell::LoadURI.
00167   loadInfo->SetOwner(principal);
00168 
00169   // Don't set referrer if we're the system principal.
00170   // XXXbz not like it matters -- the URI of the system principal is
00171   // null on branch...
00172   if (principal != sysPrin) {
00173     nsCOMPtr<nsIURI> referrer;  
00174     rv = principal->GetURI(getter_AddRefs(referrer));
00175     NS_ENSURE_SUCCESS(rv, rv);
00176 
00177     loadInfo->SetReferrer(referrer);
00178   }
00179 
00180   // Kick off the load...
00181   rv = mDocShell->LoadURI(uri, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE,
00182                           PR_FALSE);
00183   NS_ASSERTION(NS_SUCCEEDED(rv), "failed to load URL");
00184 
00185   return rv;
00186 }
00187 
00188 NS_IMETHODIMP
00189 nsFrameLoader::GetDocShell(nsIDocShell **aDocShell)
00190 {
00191   *aDocShell = nsnull;
00192 
00193   // If we have an owner, make sure we have a docshell and return
00194   // that. If not, we're most likely in the middle of being torn down,
00195   // then we just return null.
00196   if (mOwnerContent) {
00197     nsresult rv = EnsureDocShell();
00198     NS_ENSURE_SUCCESS(rv, rv);
00199   }
00200 
00201   *aDocShell = mDocShell;
00202   NS_IF_ADDREF(*aDocShell);
00203 
00204   return NS_OK;
00205 }
00206 
00207 NS_IMETHODIMP
00208 nsFrameLoader::Destroy()
00209 {
00210   if (mOwnerContent) {
00211     nsCOMPtr<nsIDocument> doc = mOwnerContent->GetDocument();
00212 
00213     if (doc) {
00214       doc->SetSubDocumentFor(mOwnerContent, nsnull);
00215     }
00216 
00217     mOwnerContent = nsnull;
00218   }
00219 
00220   // Let the tree owner know we're gone.
00221   if (mIsTopLevelContent) {
00222     nsCOMPtr<nsIDocShellTreeItem> ourItem = do_QueryInterface(mDocShell);
00223     if (ourItem) {
00224       nsCOMPtr<nsIDocShellTreeItem> parentItem;
00225       ourItem->GetParent(getter_AddRefs(parentItem));
00226       nsCOMPtr<nsIDocShellTreeOwner> owner = do_GetInterface(parentItem);
00227       nsCOMPtr<nsIDocShellTreeOwner_MOZILLA_1_8_BRANCH> owner2 =
00228         do_QueryInterface(owner);
00229       if (owner2) {
00230         owner2->ContentShellRemoved(ourItem);
00231       }
00232     }
00233   }
00234   
00235   // Let our window know that we are gone
00236   nsCOMPtr<nsPIDOMWindow> win_private(do_GetInterface(mDocShell));
00237   if (win_private) {
00238     win_private->SetFrameElementInternal(nsnull);
00239   }
00240   
00241   nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
00242 
00243   if (base_win) {
00244     base_win->Destroy();
00245   }
00246 
00247   mDocShell = nsnull;
00248   return NS_OK;
00249 }
00250 
00251 NS_IMETHODIMP
00252 nsFrameLoader::GetDepthTooGreat(PRBool* aDepthTooGreat)
00253 {
00254   *aDepthTooGreat = mDepthTooGreat;
00255   return NS_OK;
00256 }
00257 
00258 nsresult
00259 nsFrameLoader::EnsureDocShell()
00260 {
00261   if (mDocShell) {
00262     return NS_OK;
00263   }
00264 
00265   // Get our parent docshell off the document of mOwnerContent
00266   // XXXbz this is such a total hack.... We really need to have a
00267   // better setup for doing this.
00268   nsIDocument* doc = mOwnerContent->GetDocument();
00269   if (!doc) {
00270     return NS_ERROR_UNEXPECTED;
00271   }
00272 
00273   nsCOMPtr<nsIWebNavigation> parentAsWebNav =
00274     do_GetInterface(doc->GetScriptGlobalObject());
00275 
00276   // Create the docshell...
00277   mDocShell = do_CreateInstance("@mozilla.org/webshell;1");
00278   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
00279 
00280   // Get the frame name and tell the docshell about it.
00281   nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
00282   NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
00283   nsAutoString frameName;
00284 
00285   // Don't use mOwnerContent->GetNameSpaceID() here since it returns
00286   // kNameSpaceID_XHTML for both HTML and XHTML, see bug 183683.
00287   nsINodeInfo* ni = mOwnerContent->GetNodeInfo();
00288   if (ni && ni->NamespaceID() == kNameSpaceID_XHTML) {
00289     mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::id, frameName);
00290   } else {
00291     mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::name, frameName);
00292     // XXX if no NAME then use ID, after a transition period this will be
00293     // changed so that XUL only uses ID too (bug 254284).
00294     if (frameName.IsEmpty() && ni && ni->NamespaceID() == kNameSpaceID_XUL) {
00295       mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::id, frameName);
00296     }
00297   }
00298 
00299   if (!frameName.IsEmpty()) {
00300     docShellAsItem->SetName(frameName.get());
00301   }
00302 
00303   // If our container is a web-shell, inform it that it has a new
00304   // child. If it's not a web-shell then some things will not operate
00305   // properly.
00306 
00307   nsCOMPtr<nsIDocShellTreeNode> parentAsNode(do_QueryInterface(parentAsWebNav));
00308   if (parentAsNode) {
00309     // Note: This logic duplicates a lot of logic in
00310     // nsSubDocumentFrame::AttributeChanged.  We should fix that.
00311 
00312     nsCOMPtr<nsIDocShellTreeItem> parentAsItem =
00313       do_QueryInterface(parentAsNode);
00314 
00315     PRInt32 parentType;
00316     parentAsItem->GetItemType(&parentType);
00317 
00318     nsAutoString value;
00319     PRBool isContent = PR_FALSE;
00320 
00321     if (mOwnerContent->IsContentOfType(nsIContent::eXUL)) {
00322       mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::type, value);
00323     }
00324 
00325     // we accept "content" and "content-xxx" values.
00326     // at time of writing, we expect "xxx" to be "primary" or "targetable", but
00327     // someday it might be an integer expressing priority or something else.
00328 
00329     isContent = value.LowerCaseEqualsLiteral("content") ||
00330       StringBeginsWith(value, NS_LITERAL_STRING("content-"),
00331                        nsCaseInsensitiveStringComparator());
00332 
00333     if (isContent) {
00334       // The web shell's type is content.
00335 
00336       docShellAsItem->SetItemType(nsIDocShellTreeItem::typeContent);
00337     } else {
00338       // Inherit our type from our parent webshell.  If it is
00339       // chrome, we'll be chrome.  If it is content, we'll be
00340       // content.
00341 
00342       docShellAsItem->SetItemType(parentType);
00343     }
00344 
00345     parentAsNode->AddChild(docShellAsItem);
00346 
00347     if (parentType == nsIDocShellTreeItem::typeChrome && isContent) {
00348       mIsTopLevelContent = PR_TRUE;
00349       
00350       // XXXbz why is this in content code, exactly?  We should handle
00351       // this some other way.....  Not sure how yet.
00352       nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
00353       parentAsItem->GetTreeOwner(getter_AddRefs(parentTreeOwner));
00354       nsCOMPtr<nsIDocShellTreeOwner_MOZILLA_1_8_BRANCH> owner2 =
00355         do_QueryInterface(parentTreeOwner);
00356 
00357       PRBool is_primary = value.LowerCaseEqualsLiteral("content-primary");
00358 
00359       if (owner2) {
00360         PRBool is_targetable = is_primary ||
00361           value.LowerCaseEqualsLiteral("content-targetable");
00362         owner2->ContentShellAdded2(docShellAsItem, is_primary, is_targetable,
00363                                    value);
00364       } else if (parentTreeOwner) {
00365         parentTreeOwner->ContentShellAdded(docShellAsItem, is_primary,
00366                                            value.get());
00367       }
00368     }
00369 
00370     // Make sure all shells have links back to the content element
00371     // in the nearest enclosing chrome shell.
00372     nsCOMPtr<nsIChromeEventHandler> chromeEventHandler;
00373 
00374     if (parentType == nsIDocShellTreeItem::typeChrome) {
00375       // Our parent shell is a chrome shell. It is therefore our nearest
00376       // enclosing chrome shell.
00377 
00378       chromeEventHandler = do_QueryInterface(mOwnerContent);
00379       NS_WARN_IF_FALSE(chromeEventHandler,
00380                        "This mContent should implement this.");
00381     } else {
00382       nsCOMPtr<nsIDocShell> parentShell(do_QueryInterface(parentAsNode));
00383 
00384       // Our parent shell is a content shell. Get the chrome event
00385       // handler from it and use that for our shell as well.
00386 
00387       parentShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
00388     }
00389 
00390     mDocShell->SetChromeEventHandler(chromeEventHandler);
00391   }
00392 
00393   // This is nasty, this code (the do_GetInterface(mDocShell) below)
00394   // *must* come *after* the above call to
00395   // mDocShell->SetChromeEventHandler() for the global window to get
00396   // the right chrome event handler.
00397 
00398   // Tell the window about the frame that hosts it.
00399   nsCOMPtr<nsIDOMElement> frame_element(do_QueryInterface(mOwnerContent));
00400   NS_ASSERTION(frame_element, "frame loader owner element not a DOM element!");
00401 
00402   nsCOMPtr<nsPIDOMWindow> win_private(do_GetInterface(mDocShell));
00403   NS_ENSURE_TRUE(win_private, NS_ERROR_UNEXPECTED);
00404 
00405   win_private->SetFrameElementInternal(frame_element);
00406 
00407   nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
00408   NS_ENSURE_TRUE(base_win, NS_ERROR_UNEXPECTED);
00409 
00410   // This is kinda whacky, this call doesn't really create anything,
00411   // but it must be called to make sure things are properly
00412   // initialized
00413 
00414   base_win->Create();
00415 
00416   return NS_OK;
00417 }
00418 
00419 void
00420 nsFrameLoader::GetURL(nsString& aURI)
00421 {
00422   aURI.Truncate();
00423 
00424   if (mOwnerContent->Tag() == nsHTMLAtoms::object) {
00425     mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::data, aURI);
00426   } else {
00427     mOwnerContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::src, aURI);
00428   }
00429 }
00430 
00431 nsresult
00432 nsFrameLoader::CheckForRecursiveLoad(nsIURI* aURI)
00433 {
00434   mDepthTooGreat = PR_FALSE;
00435   
00436   NS_PRECONDITION(mDocShell, "Must have docshell here");
00437   
00438   nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(mDocShell);
00439   NS_ASSERTION(treeItem, "docshell must be a treeitem!");
00440   
00441   PRInt32 ourType;
00442   nsresult rv = treeItem->GetItemType(&ourType);
00443   if (NS_SUCCEEDED(rv) && ourType != nsIDocShellTreeItem::typeContent) {
00444     // No need to do recursion-protection here XXXbz why not??  Do we really
00445     // trust people not to screw up with non-content docshells?
00446     return NS_OK;
00447   }
00448 
00449   // Bug 8065: Don't exceed some maximum depth in content frames
00450   // (MAX_DEPTH_CONTENT_FRAMES)
00451   nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
00452   treeItem->GetSameTypeParent(getter_AddRefs(parentAsItem));
00453   PRInt32 depth = 0;
00454   while (parentAsItem) {
00455     ++depth;
00456     
00457     if (depth >= MAX_DEPTH_CONTENT_FRAMES) {
00458       mDepthTooGreat = PR_TRUE;
00459       NS_WARNING("Too many nested content frames so giving up");
00460 
00461       return NS_ERROR_UNEXPECTED; // Too deep, give up!  (silently?)
00462     }
00463 
00464     nsCOMPtr<nsIDocShellTreeItem> temp;
00465     temp.swap(parentAsItem);
00466     temp->GetSameTypeParent(getter_AddRefs(parentAsItem));
00467   }
00468   
00469   // Bug 136580: Check for recursive frame loading
00470   // pre-grab these for speed
00471   nsCOMPtr<nsIURI> cloneURI;
00472   rv = aURI->Clone(getter_AddRefs(cloneURI));
00473   NS_ENSURE_SUCCESS(rv, rv);
00474   
00475   // Bug 98158/193011: We need to ignore data after the #
00476   nsCOMPtr<nsIURL> cloneURL(do_QueryInterface(cloneURI)); // QI can fail
00477   if (cloneURL) {
00478     rv = cloneURL->SetRef(EmptyCString());
00479     NS_ENSURE_SUCCESS(rv,rv);
00480   }
00481 
00482   PRInt32 matchCount = 0;
00483   treeItem->GetSameTypeParent(getter_AddRefs(parentAsItem));
00484   while (parentAsItem) {
00485     // Check the parent URI with the URI we're loading
00486     nsCOMPtr<nsIWebNavigation> parentAsNav(do_QueryInterface(parentAsItem));
00487     if (parentAsNav) {
00488       // Does the URI match the one we're about to load?
00489       nsCOMPtr<nsIURI> parentURI;
00490       parentAsNav->GetCurrentURI(getter_AddRefs(parentURI));
00491       if (parentURI) {
00492         nsCOMPtr<nsIURI> parentClone;
00493         rv = parentURI->Clone(getter_AddRefs(parentClone));
00494         NS_ENSURE_SUCCESS(rv, rv);
00495         nsCOMPtr<nsIURL> parentURL(do_QueryInterface(parentClone));
00496         if (parentURL) {
00497           rv = parentURL->SetRef(EmptyCString());
00498           NS_ENSURE_SUCCESS(rv,rv);
00499         }
00500 
00501         PRBool equal;
00502         rv = cloneURI->Equals(parentClone, &equal);
00503         NS_ENSURE_SUCCESS(rv, rv);
00504         
00505         if (equal) {
00506           matchCount++;
00507           if (matchCount >= MAX_SAME_URL_CONTENT_FRAMES) {
00508             NS_WARNING("Too many nested content frames have the same url (recursion?) so giving up");
00509             return NS_ERROR_UNEXPECTED;
00510           }
00511         }
00512       }
00513     }
00514     nsCOMPtr<nsIDocShellTreeItem> temp;
00515     temp.swap(parentAsItem);
00516     temp->GetSameTypeParent(getter_AddRefs(parentAsItem));
00517   }
00518 
00519   return NS_OK;
00520 }