Skip to content

Commit

Permalink
Add back support for manually handling POST requests
Browse files Browse the repository at this point in the history
We used to ignore the request method and only decide if we should run
a command or a page initial "GET" based on the headers. This got rewritten
in 4.2 and thus broke anyone manually doing and handling POST requests.
It has been "expoited" by some users to do "API requests", but can
also be used to implement classic POST forms,
as a no-JS or shitty network fallback.

To implement a no-JS form in DotVVM with this patch:

    <form method="POST">
        <input type="text" name="something" />
        <button type=submit name=submit value=my-form>Submit</button>
    </form>

and handle it in view model:

    public override async Task Load()
    {
        var req = Context.HttpContext.Request;
        if (req.Method == "POST")
        {
            using var body = new StreamReader(req.Body);
            var data = HttpUtility.ParseQueryString(await body.ReadToEndAsync());
            var submitedForm = data["submit"];
            var something = data["something"];
        }
    }
  • Loading branch information
exyi committed Nov 25, 2023
1 parent 2b6220a commit 5394d07
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 5 deletions.
7 changes: 2 additions & 5 deletions src/Framework/Framework/Hosting/DotvvmPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,8 @@ async Task RespondWithStaticCommandValidationFailure(ActionInfo action, IDotvvmR
async Task ValidateSecFetchHeaders(IDotvvmRequestContext context)
{
var route = context.Route?.RouteName;
var isPost = context.HttpContext.Request.Method switch {
"POST" => true,
"GET" => false,
_ => throw new NotSupportedException()
};
var requestType = DotvvmRequestContext.DetermineRequestType(context.HttpContext);
var isPost = requestType is DotvvmRequestType.Command or DotvvmRequestType.StaticCommand;
var checksAllowed = (isPost ? SecurityConfiguration.VerifySecFetchForCommands : SecurityConfiguration.VerifySecFetchForPages).IsEnabledForRoute(route);
var dest = context.HttpContext.Request.Headers["Sec-Fetch-Dest"];
var site = context.HttpContext.Request.Headers["Sec-Fetch-Site"];
Expand Down
3 changes: 3 additions & 0 deletions src/Framework/Framework/Hosting/DotvvmRequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ public static DotvvmRequestType DetermineRequestType(IHttpContext context)
{
return DotvvmRequestType.Command;
}
// Unknown POST request is treated as a Navigate request
// it is useful for submitting classic <form method=POST> elements (as a no-JS fallback, login forms, etc.)
return DotvvmRequestType.Navigate;
}
return DotvvmRequestType.Unknown;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using DotVVM.Framework.ViewModel;

namespace DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.NoJsForm
{
public class NoJsFormViewModel : DotvvmViewModelBase
{
public string Form1Value { get; set; }
public string Form2Value { get; set; }

public override async Task Load()
{
var req = Context.HttpContext.Request;
if (req.Method == "POST")
{
using var body = req.Body;
var data = HttpUtility.ParseQueryString(await new StreamReader(body).ReadToEndAsync());

Check failure on line 23 in src/Samples/Common/ViewModels/FeatureSamples/NoJsForm/NoJsFormViewModel.cs

View workflow job for this annotation

GitHub Actions / UI tests (chrome, windows-2022, Development, Default)

The name 'HttpUtility' does not exist in the current context

Check failure on line 23 in src/Samples/Common/ViewModels/FeatureSamples/NoJsForm/NoJsFormViewModel.cs

View workflow job for this annotation

GitHub Actions / Build all projects without errors

The name 'HttpUtility' does not exist in the current context
var submit = data["submit"];
if (submit == "form1")
{
Form1Value = data["text"];
}
else if (submit == "form2")
{
Form2Value = data["text"];
}
}
}
}
}

35 changes: 35 additions & 0 deletions src/Samples/Common/Views/FeatureSamples/NoJsForm/NoJsForm.dothtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@viewModel DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.NoJsForm.NoJsFormViewModel, DotVVM.Samples.Common

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div RenderSettings.Mode=Server>
<fieldset>
<legend>Form 1</legend>
<form method="POST">
<input id=input1 type="text" name="text" />
<button id=submit1 type=submit name=submit value=form1>Submit</button>

<p Visible={value: Form1Value != null}>
Last submitted value: <span id=result1>{{value: Form1Value}}</span>
</p>
</form>
</fieldset>

<fieldset>
<legend>Form 2</legend>
<form method="POST">
<input id=input2 type="text" name="text" />
<button id=submit2 type=submit name=submit value=form2>Submit</button>

<p Visible={value: Form2Value != null}>
Last submitted value: <span id=result2>{{value: Form2Value}}</span>
</p>
</form>
</fieldset>
</div>
</body>
</html>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions src/Samples/Tests/Tests/Feature/NoJsFormTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
using DotVVM.Samples.Tests.Base;
using DotVVM.Testing.Abstractions;
using Riganti.Selenium.Core;
using Riganti.Selenium.DotVVM;
using Xunit;
using Xunit.Abstractions;

namespace DotVVM.Samples.Tests.Feature
{
public class NoJsFormTests : AppSeleniumTest
{
[Fact]
public void Feature_NoJsForm_NoJsForm()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_NoJsForm_NoJsForm);

browser.SendKeys("#input1", "Q");
browser.Click("#submit1");
AssertUI.InnerTextEquals(browser.First("#result1"), "Q");

browser.SendKeys("#input2", "W");
browser.Click("#submit2");
AssertUI.InnerTextEquals(browser.First("#result2"), "W");
});
}

public NoJsFormTests(ITestOutputHelper output) : base(output)
{
}
}
}

0 comments on commit 5394d07

Please sign in to comment.