--- a/content/html/content/public/nsIForm.h
+++ b/content/html/content/public/nsIForm.h
@@ -39,18 +39,20 @@
#include "nsISupports.h"
#include "nsAString.h"
class nsIFormControl;
class nsISimpleEnumerator;
class nsIURI;
-#define NS_FORM_METHOD_GET 0
-#define NS_FORM_METHOD_POST 1
+#define NS_FORM_METHOD_GET 0
+#define NS_FORM_METHOD_POST 1
+#define NS_FORM_METHOD_PUT 2
+#define NS_FORM_METHOD_DELETE 3
#define NS_FORM_ENCTYPE_URLENCODED 0
#define NS_FORM_ENCTYPE_MULTIPART 1
#define NS_FORM_ENCTYPE_TEXTPLAIN 2
// IID for the nsIForm interface
#define NS_IFORM_IID \
{ 0x27f1ff6c, 0xeb78, 0x405b, \
{ 0xa6, 0xeb, 0xf0, 0xce, 0xa8, 0x30, 0x85, 0x58 } }
--- a/content/html/content/src/nsFormSubmissionConstants.h
+++ b/content/html/content/src/nsFormSubmissionConstants.h
@@ -34,18 +34,20 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsFormSubmissionConstants_h__
#define nsFormSubmissionConstants_h__
static const nsAttrValue::EnumTable kFormMethodTable[] = {
- { "get", NS_FORM_METHOD_GET },
- { "post", NS_FORM_METHOD_POST },
+ { "get", NS_FORM_METHOD_GET },
+ { "post", NS_FORM_METHOD_POST },
+ { "put", NS_FORM_METHOD_PUT },
+ { "delete", NS_FORM_METHOD_DELETE },
{ 0 }
};
// Default method is 'get'.
static const nsAttrValue::EnumTable* kFormDefaultMethod = &kFormMethodTable[0];
static const nsAttrValue::EnumTable kFormEnctypeTable[] = {
{ "multipart/form-data", NS_FORM_ENCTYPE_MULTIPART },
{ "application/x-www-form-urlencoded", NS_FORM_ENCTYPE_URLENCODED },
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -503,16 +503,29 @@ public:
* works for attributes in null namespace.
*
* @param aAttr name of attribute.
* @param aBaseAttr name of base attribute.
* @param aResult result value [out]
*/
NS_HIDDEN_(nsresult) GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsAString& aResult);
+ /**
+ * Helper method for NS_IMPL_ENUM_ATTR_DEFAULT_VALUE.
+ * Gets the enum value string of an attribute and using a default value if
+ * the attribute is missing or the string is an invalid enum value.
+ *
+ * @param aType the name of the attribute.
+ * @param aDefault the default value if the attribute is missing or invalid.
+ * @param aResult string corresponding to the value [out].
+ */
+ NS_HIDDEN_(nsresult) GetEnumAttr(nsIAtom* aAttr,
+ const char* aDefault,
+ nsAString& aResult);
+
protected:
/**
* Add/remove this element to the documents name cache
*/
void AddToNameTable(nsIAtom* aName) {
NS_ASSERTION(HasFlag(NODE_HAS_NAME), "Node lacking NODE_HAS_NAME flag");
nsIDocument* doc = GetCurrentDoc();
if (doc && !IsInAnonymousSubtree()) {
@@ -690,29 +703,16 @@ protected:
* attributes in null namespace.
*
* @param aAttr name of attribute.
* @param aResult result value [out]
*/
NS_HIDDEN_(nsresult) GetURIListAttr(nsIAtom* aAttr, nsAString& aResult);
/**
- * Helper method for NS_IMPL_ENUM_ATTR_DEFAULT_VALUE.
- * Gets the enum value string of an attribute and using a default value if
- * the attribute is missing or the string is an invalid enum value.
- *
- * @param aType the name of the attribute.
- * @param aDefault the default value if the attribute is missing or invalid.
- * @param aResult string corresponding to the value [out].
- */
- NS_HIDDEN_(nsresult) GetEnumAttr(nsIAtom* aAttr,
- const char* aDefault,
- nsAString& aResult);
-
- /**
* Locates the nsIEditor associated with this node. In general this is
* equivalent to GetEditorInternal(), but for designmode or contenteditable,
* this may need to get an editor that's not actually on this element's
* associated TextControlFrame. This is used by the spellchecking routines
* to get the editor affected by changing the spellcheck attribute on this
* node.
*/
virtual already_AddRefed<nsIEditor> GetAssociatedEditor();
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -873,21 +873,35 @@ nsHTMLFormElement::SubmitSubmission(nsFo
nsAutoHandlingUserInputStatePusher userInpStatePusher(mSubmitInitiatedFromUserInput, PR_FALSE);
nsCOMPtr<nsIInputStream> postDataStream;
rv = aFormSubmission->GetEncodedSubmission(actionURI,
getter_AddRefs(postDataStream));
NS_ENSURE_SUBMIT_SUCCESS(rv);
+ nsAutoString method;
+ if (originatingElement &&
+ originatingElement->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::formmethod)) {
+ if (!originatingElement->IsHTML()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ static_cast<nsGenericHTMLElement*>(originatingElement)->
+ GetEnumAttr(nsGkAtoms::formmethod, kFormDefaultMethod->tag, method);
+ } else {
+ GetEnumAttr(nsGkAtoms::method, kFormDefaultMethod->tag, method);
+ }
+
rv = linkHandler->OnLinkClickSync(this, actionURI,
target.get(),
postDataStream, nsnull,
getter_AddRefs(docShell),
- getter_AddRefs(mSubmittingRequest));
+ getter_AddRefs(mSubmittingRequest),
+ NS_LossyConvertUTF16toASCII(method).get());
NS_ENSURE_SUBMIT_SUCCESS(rv);
}
// Even if the submit succeeds, it's possible for there to be no docshell
// or request; for example, if it's to a named anchor within the same page
// the submit will not really do anything.
if (docShell) {
// If the channel is pending, we have to listen for web progress.
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/583288_redirect_server.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+ // Redirect to another domain.
+ // Using 307 to keep the method.
+ response.setStatusLine(null, 307, "Temp");
+ response.setHeader("Location",
+ "http://example.org:80/tests/content/html/content/test/583288_submit_server.sjs");
+}
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/583288_submit_server.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response)
+{
+ // Send the HTTP method.
+ response.write(request.method);
+}
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -207,12 +207,17 @@ include $(topsrcdir)/config/rules.mk
test_bug561634.html \
test_bug588683-1.html \
test_bug588683-2.html \
test_bug588683-3.html \
test_bug588683-4.html \
test_bug590353-1.html \
test_bug590353-2.html \
test_bug593689.html \
+ test_bug583288-1.html \
+ test_bug583288-2.html \
+ test_bug583288-3.html \
+ 583288_submit_server.sjs \
+ 583288_redirect_server.sjs \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug583288-1.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583288
+-->
+<head>
+ <title>Test for Bug 583288</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload='runTests();'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583288">Mozilla Bug 583288</a>
+<p id="display"></p>
+<style>
+ iframe { width: 70px; height: 40px;}
+</style>
+<!--<div id="content" style="display: none">-->
+<div id="content">
+ <iframe name='get' id='get'></iframe>
+ <iframe name='post' id='post'></iframe>
+ <iframe name='put' id='put'></iframe>
+ <iframe name='del' id='del'></iframe>
+ <iframe name='get2' id='get2'></iframe>
+ <iframe name='post2' id='post2'></iframe>
+ <iframe name='put2' id='put2'></iframe>
+ <iframe name='del2' id='del2'></iframe>
+
+ <form action="583288_submit_server.sjs" target='get' method='get'>
+ </form>
+ <form action="583288_submit_server.sjs" target='post' method='post'>
+ </form>
+ <form action="583288_submit_server.sjs" target='put' method='put'>
+ </form>
+ <form action="583288_submit_server.sjs" target='del' method='delete'>
+ </form>
+
+ <form action="583288_submit_server.sjs">
+ <input type='submit' id='iget' value='get' formtarget='get2' formmethod='get'>
+ </form>
+ <form action="583288_submit_server.sjs">
+ <input type='submit' id='ipost' value='post' formtarget='post2' formmethod='post'>
+ </form>
+ <form action="583288_submit_server.sjs">
+ <input type='submit' id='iput' value='put' formtarget='put2' formmethod='put'>
+ </form>
+ <form action="583288_submit_server.sjs">
+ <input type='submit' id='idel' value='delete' formtarget='del2' formmethod='delete'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583288 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gTestResults = {
+ get: "GET",
+ get2: "GET",
+ post: "POST",
+ post2: "POST",
+ put: "PUT",
+ put2: "PUT",
+ del: "DELETE",
+ del2: "DELETE",
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('get'),
+ document.getElementById('get2'),
+ document.getElementById('post'),
+ document.getElementById('post2'),
+ document.getElementById('put'),
+ document.getElementById('put2'),
+ document.getElementById('del'),
+ document.getElementById('del2'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<frames.length; ++i) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ // The four first forms can be submitted with .submit().
+ for (var i=0; i<4; ++i) {
+ document.forms[i].submit();
+ }
+
+ /**
+ * We are going to focus each element before interacting with them so we are
+ * sure they will be visible in the iframe.
+ * Considering we are using synthesizeKey that may be not required.
+ *
+ * Focusing the first element (id='iget') is launching the tests.
+ */
+ document.getElementById('iget').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('ipost').focus();
+ }, false);
+
+ document.getElementById('ipost').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('iput').focus();
+ }, false);
+
+ document.getElementById('iput').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('idel').focus();
+ }, false);
+
+ document.getElementById('idel').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ document.getElementById('iget').focus();
+}
+
+function frameLoaded(aFrame) {
+ // Check if formaction/action has the correct behavior.
+ is(aFrame.contentDocument.documentElement.textContent, gTestResults[aFrame.name],
+ "the method used during the form submission should be " +
+ gTestResults[aFrame.name]);
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug583288-2.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583288
+-->
+<head>
+ <title>Test for Bug 583288</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload='runTests();'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583288">Mozilla Bug 583288</a>
+<p id="display"></p>
+<style>
+ iframe { width: 70px; height: 40px;}
+</style>
+<!--<div id="content" style="display: none">-->
+<div id="content">
+ <iframe name='get' id='get'></iframe>
+ <iframe name='post' id='post'></iframe>
+ <iframe name='put' id='put'></iframe>
+ <iframe name='del' id='del'></iframe>
+
+ <form action="http://example.org:80/tests/content/html/content/test/583288_submit_server.sjs" target='get' method='get'>
+ </form>
+ <form action="http://example.org:80/tests/content/html/content/test/583288_submit_server.sjs" target='post' method='post'>
+ </form>
+ <form action="http://example.org:80/tests/content/html/content/test/583288_submit_server.sjs" target='put' method='put'>
+ </form>
+ <form action="http://example.org:80/tests/content/html/content/test/583288_submit_server.sjs" target='del' method='delete'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583288 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gLoaded = 0;
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('get'),
+ document.getElementById('post'),
+ document.getElementById('put'),
+ document.getElementById('del'),
+ ];
+
+ for (var i=0; i<2; ++i) {
+ frames[i].setAttribute('onload', "frameShouldLoad();");
+ }
+ for (var i=2; i<4; ++i) {
+ frames[i].setAttribute('onload', "frameShouldNotLoad();");
+ }
+
+ // The four forms can be submitted with .submit().
+ // Submitting those which should be blocked so we can considering if the the
+ // last ones are submitted, that means the firsts were blocked.
+ for (var i=3; i>=0; --i) {
+ document.forms[i].submit();
+ }
+
+ // After the two first succefull submissions, we are going to stop the test.
+ // The expected successfull submissions have been requested at the end so we
+ // expect them at the end. So if the two firsts ones didn't show up, we are
+ // assuming they have been blocked.
+ // Unfortunately, it doesn't sound like there is a better way to test that.
+ // The worst thing that can happen here is to have a random green (if the
+ // first two submissions were not blocked but came after the last two).
+}
+
+function frameShouldLoad() {
+ ok(true, "The form submission should succeed.");
+ if (++gLoaded == 2) {
+ finished();
+ }
+}
+
+function frameShouldNotLoad() {
+ ok(false, "The form submission should have been blocked");
+ if (++gLoaded == 2) {
+ finished();
+ }
+}
+
+function finished()
+{
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug583288-3.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583288
+-->
+<head>
+ <title>Test for Bug 583288</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload='runTests();'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583288">Mozilla Bug 583288</a>
+<p id="display"></p>
+<style>
+ iframe { width: 70px; height: 40px;}
+</style>
+<!--<div id="content" style="display: none">-->
+<div id="content">
+ <iframe name='get' id='get'></iframe>
+ <iframe name='post' id='post'></iframe>
+ <iframe name='put' id='put'></iframe>
+ <iframe name='del' id='del'></iframe>
+
+ <form action="583288_redirect_server.sjs" target='get' method='get'>
+ </form>
+ <form action="583288_redirect_server.sjs" target='post' method='post'>
+ </form>
+ <form action="583288_redirect_server.sjs" target='put' method='put'>
+ </form>
+ <form action="583288_redirect_server.sjs" target='del' method='delete'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583288 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gTestResults = {
+ get: "GET",
+ post: "POST",
+ put: "",
+ del: "",
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('get'),
+ document.getElementById('post'),
+ document.getElementById('put'),
+ document.getElementById('del'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<gPendingLoad; ++i) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ // The four forms can be submitted with .submit().
+ for (var i=0; i<4; ++i) {
+ document.forms[i].submit();
+ }
+}
+
+function frameLoaded(aFrame) {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ is(aFrame.contentDocument.documentElement.textContent, gTestResults[aFrame.name],
+ "cross-origin submission with redirection should work");
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/html/content/test/test_bug585508.html
+++ b/content/html/content/test/test_bug585508.html
@@ -25,21 +25,19 @@ var enctypeTestData = [
// Invalid values.
[ "", " ", "foo", "multipart/foo" ]
];
var methodTestData = [
// Default value.
[ "get" ],
// Valid values.
- [ "get", "post" ],
+ [ "get", "post", "put", "delete" ],
// Invalid values.
[ "", " ", "foo" ],
- // TODO values, see bug 583289 and bug 583288.
- [ "delete", "put" ],
];
function checkAttribute(form, attrName, idlName, data)
{
is(form.getAttribute(attrName), null,
"By default " + attrName + " content attribute should be null");
is(form[idlName], data[0][0],
"By default " + idlName + " IDL attribute should be equal to " +
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1407,17 +1407,18 @@ nsDocShell::LoadURI(nsIURI * aURI,
target.get(),
nsnull, // No type hint
postStream,
headersStream,
loadType,
nsnull, // No SHEntry
aFirstParty,
nsnull, // No nsIDocShell
- nsnull); // No nsIRequest
+ nsnull, // No nsIRequest
+ nsnull); // Use default HTTP method
}
NS_IMETHODIMP
nsDocShell::LoadStream(nsIInputStream *aStream, nsIURI * aURI,
const nsACString &aContentType,
const nsACString &aContentCharset,
nsIDocShellLoadInfo * aLoadInfo)
{
@@ -4090,17 +4091,17 @@ nsDocShell::LoadErrorPage(nsIURI *aURI,
nsCOMPtr<nsIURI> errorPageURI;
nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
NS_ENSURE_SUCCESS(rv, rv);
return InternalLoad(errorPageURI, nsnull, nsnull,
INTERNAL_LOAD_FLAGS_INHERIT_OWNER, nsnull, nsnull,
nsnull, nsnull, LOAD_ERROR_PAGE,
- nsnull, PR_TRUE, nsnull, nsnull);
+ nsnull, PR_TRUE, nsnull, nsnull, nsnull);
}
NS_IMETHODIMP
nsDocShell::Reload(PRUint32 aReloadFlags)
{
if (!IsNavigationAllowed()) {
return NS_OK; // JS may not handle returning of an error code
@@ -4154,17 +4155,18 @@ nsDocShell::Reload(PRUint32 aReloadFlags
nsnull, // No window target
NS_LossyConvertUTF16toASCII(contentTypeHint).get(),
nsnull, // No post data
nsnull, // No headers data
loadType, // Load type
nsnull, // No SHEntry
PR_TRUE,
nsnull, // No nsIDocShell
- nsnull); // No nsIRequest
+ nsnull, // No nsIRequest
+ nsnull); // Use default HTTP method
}
return rv;
}
NS_IMETHODIMP
nsDocShell::Stop(PRUint32 aStopFlags)
@@ -5864,32 +5866,63 @@ nsDocShell::OnStateChange(nsIWebProgress
NS_IMETHODIMP
nsDocShell::OnLocationChange(nsIWebProgress * aProgress,
nsIRequest * aRequest, nsIURI * aURI)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
-void
+nsresult
nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
nsIChannel* aNewChannel,
PRUint32 aRedirectFlags,
PRUint32 aStateFlags)
{
NS_ASSERTION(aStateFlags & STATE_REDIRECTING,
"Calling OnRedirectStateChange when there is no redirect");
if (!(aStateFlags & STATE_IS_DOCUMENT))
- return; // not a toplevel document
+ return NS_OK; // not a toplevel document
nsCOMPtr<nsIURI> oldURI, newURI;
aOldChannel->GetURI(getter_AddRefs(oldURI));
aNewChannel->GetURI(getter_AddRefs(newURI));
if (!oldURI || !newURI) {
- return;
+ return NS_OK;
+ }
+
+ // HTTP channel with unsafe methods should not be redirected to a cross-domain.
+ if (!ChannelIsSafeMethod(aNewChannel)) {
+ // This code is very similar to the code of nsSameOriginChecker in
+ // nsContentUtils but we can't use nsSameOriginChecker because it
+ // needs to use a channel callback (which we already use).
+ // If nsSameOriginChecker happens to not use a channel callback
+ // anymore, this code would be a good candidate for refactoring.
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ nsresult rv;
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+
+ rv = secMan->GetChannelPrincipal(aOldChannel,
+ getter_AddRefs(oldPrincipal));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ NS_ASSERTION(oldPrincipal, "oldPrincipal should not be null!");
+
+ nsCOMPtr<nsIURI> newOriginalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ rv = oldPrincipal->CheckMayLoad(newURI, PR_FALSE);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = oldPrincipal->CheckMayLoad(newOriginalURI, PR_FALSE);
+ }
+
+ // The requested tried to be redirected, we have to cancel it.
+ NS_ENSURE_SUCCESS(rv, rv);
}
// Below a URI visit is saved (see AddURIVisit method doc).
// The visit chain looks something like:
// ...
// Site N - 1
// => Site N
// (redirect to =>) Site N + 1 (we are here!)
@@ -5930,16 +5963,18 @@ nsDocShell::OnRedirectStateChange(nsICha
appCacheChannel->SetChooseApplicationCache(ShouldCheckAppCache(newURI));
}
if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
mLoadType = LOAD_NORMAL_REPLACE;
SetHistoryEntry(&mLSHE, nsnull);
}
+
+ return NS_OK;
}
NS_IMETHODIMP
nsDocShell::OnStatusChange(nsIWebProgress * aWebProgress,
nsIRequest * aRequest,
nsresult aStatus, const PRUnichar * aMessage)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
@@ -6301,17 +6336,18 @@ nsDocShell::EndPageLoad(nsIWebProgress *
nsnull, // No window target
nsnull, // No type hint
inputStream, // Post data stream
nsnull, // No headers stream
LOAD_RELOAD_BYPASS_PROXY_AND_CACHE,// Load type
nsnull, // No SHEntry
PR_TRUE, // first party site
nsnull, // No nsIDocShell
- nsnull); // No nsIRequest
+ nsnull, // No nsIRequest
+ nsnull); // Use default HTTP method
}
else {
DisplayLoadError(aStatus, url, nsnull, aChannel);
}
}
} // if we have a host
return NS_OK;
@@ -7745,17 +7781,17 @@ public:
mTypeHint = aTypeHint;
}
}
NS_IMETHOD Run() {
return mDocShell->InternalLoad(mURI, mReferrer, mOwner, mFlags,
nsnull, mTypeHint.get(),
mPostData, mHeadersData, mLoadType,
- mSHEntry, mFirstParty, nsnull, nsnull);
+ mSHEntry, mFirstParty, nsnull, nsnull, nsnull);
}
private:
// Use IDL strings so .get() returns null by default
nsXPIDLString mWindowTarget;
nsXPIDLCString mTypeHint;
@@ -7779,17 +7815,18 @@ nsDocShell::InternalLoad(nsIURI * aURI,
const PRUnichar *aWindowTarget,
const char* aTypeHint,
nsIInputStream * aPostData,
nsIInputStream * aHeadersData,
PRUint32 aLoadType,
nsISHEntry * aSHEntry,
PRBool aFirstParty,
nsIDocShell** aDocShell,
- nsIRequest** aRequest)
+ nsIRequest** aRequest,
+ const char* aHttpMethod)
{
nsresult rv = NS_OK;
#ifdef PR_LOGGING
if (gDocShellLeakLog && PR_LOG_TEST(gDocShellLeakLog, PR_LOG_DEBUG)) {
nsCAutoString spec;
if (aURI)
aURI->GetSpec(spec);
@@ -7981,17 +8018,18 @@ nsDocShell::InternalLoad(nsIURI * aURI,
nsnull, // No window target
aTypeHint,
aPostData,
aHeadersData,
aLoadType,
aSHEntry,
aFirstParty,
aDocShell,
- aRequest);
+ aRequest,
+ aHttpMethod);
if (rv == NS_ERROR_NO_CONTENT) {
// XXXbz except we never reach this code!
if (isNewWindow) {
//
// At this point, a new window has been created, but the
// URI did not have any data associated with it...
//
// So, the best we can do, is to tear down the new window
@@ -8416,17 +8454,18 @@ nsDocShell::InternalLoad(nsIURI * aURI,
nsCOMPtr<nsIRequest> req;
rv = DoURILoad(aURI, aReferrer,
!(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
owner, aTypeHint, aPostData, aHeadersData, aFirstParty,
aDocShell, getter_AddRefs(req),
(aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
(aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
- (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0);
+ (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
+ aHttpMethod);
if (req && aRequest)
NS_ADDREF(*aRequest = req);
if (NS_FAILED(rv)) {
nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
DisplayLoadError(rv, aURI, nsnull, chan);
}
@@ -8497,17 +8536,18 @@ nsDocShell::DoURILoad(nsIURI * aURI,
const char * aTypeHint,
nsIInputStream * aPostData,
nsIInputStream * aHeadersData,
PRBool aFirstParty,
nsIDocShell ** aDocShell,
nsIRequest ** aRequest,
PRBool aIsNewWindowTarget,
PRBool aBypassClassifier,
- PRBool aForceAllowCookies)
+ PRBool aForceAllowCookies,
+ const char* aHttpMethod)
{
nsresult rv;
nsCOMPtr<nsIURILoader> uriLoader;
uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
@@ -8680,16 +8720,30 @@ nsDocShell::DoURILoad(nsIURI * aURI,
if (aHeadersData) {
rv = AddHeadersToChannel(aHeadersData, httpChannel);
}
// Set the referrer explicitly
if (aReferrerURI && aSendReferrer) {
// Referrer is currenly only set for link clicks here.
httpChannel->SetReferrer(aReferrerURI);
}
+
+ // If a specific HTTP method has been requested, set it.
+ if (aHttpMethod) {
+ // Tell the cache it _has_ to open a cache entry.
+ PRUint32 loadFlags;
+ if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) {
+ channel->SetLoadFlags(loadFlags | nsICachingChannel::FORCE_OPEN_CACHE_ENTRY);
+ }
+
+ // The method name have to be correct.
+ // Otherwise SetRequestMethod will return a failure.
+ rv = httpChannel->SetRequestMethod(nsDependentCString(aHttpMethod));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
}
//
// Set the owner of the channel, but only for channels that can't
// provide their own security context.
//
// XXX: Is seems wrong that the owner is ignored - even if one is
// supplied) unless the URI is javascript or data or about:blank.
// XXX: If this is ever changed, check all callers for what owners they're
@@ -8730,16 +8784,24 @@ nsDocShell::DoURILoad(nsIURI * aURI,
if (secMan &&
NS_SUCCEEDED(secMan->IsSystemPrincipal(ownerPrincipal,
&isSystem)) &&
!isSystem) {
channel->SetOwner(aOwner);
}
}
+ // If a specific HTTP channel has been set and it is not a safe method,
+ // we should prevent cross-origin requests.
+ if (aHttpMethod && ownerPrincipal && !ChannelIsSafeMethod(channel)) {
+ if (NS_FAILED(ownerPrincipal->CheckMayLoad(aURI, PR_FALSE))) {
+ return NS_OK;
+ }
+ }
+
nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
if (scriptChannel) {
// Allow execution against our context if the principals match
scriptChannel->
SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
}
if (aIsNewWindowTarget) {
@@ -9941,17 +10003,18 @@ nsDocShell::LoadHistoryEntry(nsISHEntry
nsnull, // No window target
contentType.get(), // Type hint
postData, // Post data stream
nsnull, // No headers stream
aLoadType, // Load type
aEntry, // SHEntry
PR_TRUE,
nsnull, // No nsIDocShell
- nsnull); // No nsIRequest
+ nsnull, // No nsIRequest
+ nsnull); // Use default HTTP method
return rv;
}
NS_IMETHODIMP nsDocShell::GetShouldSaveLayoutState(PRBool* aShould)
{
*aShould = PR_FALSE;
if (mOSHE) {
// Don't capture historystate and save it in history
@@ -10354,29 +10417,45 @@ NS_IMETHODIMP nsDocShell::GetHasEditingS
NS_IMETHODIMP nsDocShell::MakeEditable(PRBool inWaitForUriLoad)
{
nsresult rv = EnsureEditorData();
if (NS_FAILED(rv)) return rv;
return mEditorData->MakeEditable(inWaitForUriLoad);
}
+/* static */
bool
nsDocShell::ChannelIsPost(nsIChannel* aChannel)
{
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
if (!httpChannel) {
return false;
}
nsCAutoString method;
httpChannel->GetRequestMethod(method);
return method.Equals("POST");
}
+/* static */
+bool
+nsDocShell::ChannelIsSafeMethod(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (!httpChannel) {
+ return false;
+ }
+
+ nsCAutoString method;
+ httpChannel->GetRequestMethod(method);
+ return method.Equals("GET") || method.Equals("POST") ||
+ method.Equals("HEAD");
+}
+
void
nsDocShell::ExtractLastVisit(nsIChannel* aChannel,
nsIURI** aURI,
PRUint32* aChannelRedirectFlags)
{
nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
if (!props) {
return;
@@ -11297,17 +11376,18 @@ nsDocShell::OnLinkClick(nsIContent* aCon
NS_IMETHODIMP
nsDocShell::OnLinkClickSync(nsIContent *aContent,
nsIURI* aURI,
const PRUnichar* aTargetSpec,
nsIInputStream* aPostDataStream,
nsIInputStream* aHeadersDataStream,
nsIDocShell** aDocShell,
- nsIRequest** aRequest)
+ nsIRequest** aRequest,
+ const char* aHttpMethod)
{
// Initialize the DocShell / Request
if (aDocShell) {
*aDocShell = nsnull;
}
if (aRequest) {
*aRequest = nsnull;
}
@@ -11373,17 +11453,18 @@ nsDocShell::OnLinkClickSync(nsIContent *
target.get(), // Window target
NS_LossyConvertUTF16toASCII(typeHint).get(),
aPostDataStream, // Post data stream
aHeadersDataStream, // Headers stream
LOAD_LINK, // Load type
nsnull, // No SHEntry
PR_TRUE, // first party site
aDocShell, // DocShell out-param
- aRequest); // Request out-param
+ aRequest, // Request out-param
+ aHttpMethod); // HTTP Method
if (NS_SUCCEEDED(rv)) {
DispatchPings(aContent, referer);
}
return rv;
}
NS_IMETHODIMP
nsDocShell::OnOverLink(nsIContent* aContent,
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -238,17 +238,18 @@ public:
nsIInputStream* aPostDataStream = 0,
nsIInputStream* aHeadersDataStream = 0);
NS_IMETHOD OnLinkClickSync(nsIContent* aContent,
nsIURI* aURI,
const PRUnichar* aTargetSpec,
nsIInputStream* aPostDataStream = 0,
nsIInputStream* aHeadersDataStream = 0,
nsIDocShell** aDocShell = 0,
- nsIRequest** aRequest = 0);
+ nsIRequest** aRequest = 0,
+ const char* aHttpMethod = 0);
NS_IMETHOD OnOverLink(nsIContent* aContent,
nsIURI* aURI,
const PRUnichar* aTargetSpec);
NS_IMETHOD OnLeaveLink();
nsDocShellInfoLoadType ConvertLoadTypeToDocShellLoadInfo(PRUint32 aLoadType);
PRUint32 ConvertDocShellLoadInfoToLoadType(nsDocShellInfoLoadType aDocShellLoadType);
@@ -320,17 +321,18 @@ protected:
const char * aTypeHint,
nsIInputStream * aPostData,
nsIInputStream * aHeadersData,
PRBool firstParty,
nsIDocShell ** aDocShell,
nsIRequest ** aRequest,
PRBool aIsNewWindowTarget,
PRBool aBypassClassifier,
- PRBool aForceAllowCookies);
+ PRBool aForceAllowCookies,
+ const char* aHttpMethod);
NS_IMETHOD AddHeadersToChannel(nsIInputStream * aHeadersData,
nsIChannel * aChannel);
virtual nsresult DoChannelLoad(nsIChannel * aChannel,
nsIURILoader * aURILoader,
PRBool aBypassClassifier);
nsresult ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
PRUint32 aLoadType, PRBool * aDoHashchange);
@@ -429,30 +431,40 @@ protected:
// is passed to the callback.
static nsresult WalkHistoryEntries(nsISHEntry *aRootEntry,
nsDocShell *aRootShell,
WalkHistoryEntriesFunc aCallback,
void *aData);
// overridden from nsDocLoader, this provides more information than the
// normal OnStateChange with flags STATE_REDIRECTING
- virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
- nsIChannel* aNewChannel,
- PRUint32 aRedirectFlags,
- PRUint32 aStateFlags);
+ virtual nsresult OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ PRUint32 aRedirectFlags,
+ PRUint32 aStateFlags);
/**
* Helper function that determines if channel is an HTTP POST.
*
* @param aChannel
* The channel to test
*
* @return True iff channel is an HTTP post.
*/
- bool ChannelIsPost(nsIChannel* aChannel);
+ static bool ChannelIsPost(nsIChannel* aChannel);
+
+ /**
+ * Helper function that determines if the HTTP channel has a safe method
+ *
+ * @param aChannel The channel to test
+ *
+ * @return Whether the channel has a safe HTTP method.
+ * @note Will return false if the channel isn't an HTTP channel.
+ */
+ static bool ChannelIsSafeMethod(nsIChannel* aChannel);
/**
* Helper function that finds the last URI and its transition flags for a
* channel.
*
* This method first checks the channel's property bag to see if previous
* info has been saved. If not, it gives back the referrer of the channel.
*
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -66,17 +66,17 @@ interface nsIRequest;
interface nsISHEntry;
interface nsILayoutHistoryState;
interface nsISecureBrowserUI;
interface nsIDOMStorage;
interface nsIPrincipal;
interface nsIWebBrowserPrint;
interface nsIVariant;
-[scriptable, uuid(74470127-87eb-4f79-8293-1616fe9cb689)]
+[scriptable, uuid(0e1e1ee5-5baa-4e27-98af-3197d70d0304)]
interface nsIDocShell : nsISupports
{
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here
* however, the URL dispatcher will go through its normal process of content
* loading.
*
@@ -150,30 +150,35 @@ interface nsIDocShell : nsISupports
* @param aWindowTarget - Window target for the load.
* @param aTypeHint - A hint as to the content-type of the resulting
* data. May be null or empty if no hint.
* @param aPostDataStream - Post data stream (if POSTing)
* @param aHeadersStream - Stream containing "extra" request headers...
* @param aLoadFlags - Flags to modify load behaviour. Flags are defined
* in nsIWebNavigation.
* @param aSHEntry - Active Session History entry (if loading from SH)
+ * @param firstParty -
+ * @param aDocShell -
+ * @param aRequest -
+ * @param aHttpMethod - Force the HTTP channel to use a specific HTTP method
*/
[noscript]void internalLoad(in nsIURI aURI,
in nsIURI aReferrer,
in nsISupports aOwner,
in PRUint32 aFlags,
in wstring aWindowTarget,
in string aTypeHint,
in nsIInputStream aPostDataStream,
in nsIInputStream aHeadersStream,
in unsigned long aLoadFlags,
in nsISHEntry aSHEntry,
in boolean firstParty,
out nsIDocShell aDocShell,
- out nsIRequest aRequest);
+ out nsIRequest aRequest,
+ in string aHttpMethod);
/**
* Do either a history.pushState() or history.replaceState() operation,
* depending on the value of aReplace.
*/
void addState(in nsIVariant aData, in DOMString aTitle,
in DOMString aURL, in boolean aReplace);
--- a/uriloader/base/nsDocLoader.cpp
+++ b/uriloader/base/nsDocLoader.cpp
@@ -1604,17 +1604,19 @@ NS_IMETHODIMP nsDocLoader::AsyncOnChanne
stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
#if defined(DEBUG)
nsCOMPtr<nsIRequest> request(do_QueryInterface(aOldChannel));
NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel");
#endif /* DEBUG */
}
- OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags);
+ nsresult rv = OnRedirectStateChange(aOldChannel, aNewChannel, aFlags,
+ stateFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
FireOnStateChange(this, aOldChannel, stateFlags, NS_OK);
}
cb->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
/*
--- a/uriloader/base/nsDocLoader.h
+++ b/uriloader/base/nsDocLoader.h
@@ -64,21 +64,21 @@
struct nsRequestInfo;
struct nsListenerInfo;
/****************************************************************************
* nsDocLoader implementation...
****************************************************************************/
#define NS_THIS_DOCLOADER_IMPL_CID \
- { /* b4ec8387-98aa-4c08-93b6-6d23069c06f2 */ \
- 0xb4ec8387, \
- 0x98aa, \
- 0x4c08, \
- {0x93, 0xb6, 0x6d, 0x23, 0x06, 0x9c, 0x06, 0xf2} \
+ { \
+ 0x306cae1f, \
+ 0x90ee, \
+ 0x4e51, \
+ {0xaf, 0x27, 0x9c, 0x13, 0xde, 0xba, 0x91, 0xb6} \
}
class nsDocLoader : public nsIDocumentLoader,
public nsIRequestObserver,
public nsSupportsWeakReference,
public nsIProgressEventSink,
public nsIWebProgress,
public nsIInterfaceRequestor,
@@ -174,20 +174,22 @@ protected:
PRBool aSameURI);
// this function is overridden by the docshell, it is provided so that we
// can pass more information about redirect state (the normal OnStateChange
// doesn't get the new channel).
// @param aRedirectFlags The flags being sent to OnStateChange that
// indicate the type of redirect.
// @param aStateFlags The channel flags normally sent to OnStateChange.
- virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
- nsIChannel* aNewChannel,
- PRUint32 aRedirectFlags,
- PRUint32 aStateFlags) {}
+ //
+ // @return Something else than NS_OK if the redirection should be cancelled.
+ virtual nsresult OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ PRUint32 aRedirectFlags,
+ PRUint32 aStateFlags) { return NS_OK; }
void doStartDocumentLoad();
void doStartURLLoad(nsIRequest *request);
void doStopURLLoad(nsIRequest *request, nsresult aStatus);
void doStopDocumentLoad(nsIRequest *request, nsresult aStatus);
// Inform a parent docloader that aChild is about to call its onload
// handler.
--- a/webshell/public/nsILinkHandler.h
+++ b/webshell/public/nsILinkHandler.h
@@ -43,17 +43,17 @@
class nsIInputStream;
class nsIDocShell;
class nsIRequest;
class nsString;
class nsGUIEvent;
// Interface ID for nsILinkHandler
#define NS_ILINKHANDLER_IID \
- { 0x71627c30, 0xd3c5, 0x4ad0,{0xb5, 0x33, 0x6e, 0x01, 0x91, 0xf2, 0x79, 0x32}}
+ { 0x1fa72627, 0x646b, 0x4573,{0xb5, 0xc8, 0xb4, 0x65, 0xc6, 0x78, 0xd4, 0x9d}}
/**
* Interface used for handling clicks on links
*/
class nsILinkHandler : public nsISupports {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILINKHANDLER_IID)
@@ -82,24 +82,26 @@ public:
* @param aContent the content for the frame that generated the trigger
* @param aURI a URI obect that defines the destination for the link
* @param aTargetSpec indicates where the link is targeted (may be an empty
* string)
* @param aPostDataStream the POST data to send
* @param aHeadersDataStream ???
* @param aDocShell (out-param) the DocShell that the request was opened on
* @param aRequest the request that was opened
+ * @param aHttpMethod forces the http channel to use a specific method
*/
NS_IMETHOD OnLinkClickSync(nsIContent* aContent,
nsIURI* aURI,
const PRUnichar* aTargetSpec,
nsIInputStream* aPostDataStream = 0,
nsIInputStream* aHeadersDataStream = 0,
nsIDocShell** aDocShell = 0,
- nsIRequest** aRequest = 0) = 0;
+ nsIRequest** aRequest = 0,
+ const char* aHttpMethod = 0) = 0;
/**
* Process a mouse-over a link.
*
* @param aContent the linked content.
* @param aURI an URI object that defines the destination for the link
* @param aTargetSpec indicates where the link is targeted (it may be an empty
* string)