Skip to content

Commit 3776bb9

Browse files
committed
add configuration for rewrite rules
1 parent d552f44 commit 3776bb9

File tree

7 files changed

+105
-32
lines changed

7 files changed

+105
-32
lines changed

prime-mvc.ipr

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true">
106106
<option name="ignoredTypes">
107107
<set>
108+
<option value="java.lang.String" />
108109
<option value="java.util.List" />
109110
</set>
110111
</option>
@@ -544,7 +545,7 @@
544545
<fileSet type="namedScope" name="control-templates" pattern="file[prime-mvc]:src/main/ftl/WEB-INF/control-templates/*" />
545546
</list>
546547
</option>
547-
<DB2CodeStyleSettings version="6">
548+
<DB2CodeStyleSettings version="7">
548549
<option name="KEYWORD_CASE" value="2" />
549550
<option name="TYPE_CASE" value="2" />
550551
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -584,7 +585,7 @@
584585
<option name="EXPR_CASE_THEN_WRAP" value="true" />
585586
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
586587
</DB2CodeStyleSettings>
587-
<DerbyCodeStyleSettings version="6">
588+
<DerbyCodeStyleSettings version="7">
588589
<option name="KEYWORD_CASE" value="2" />
589590
<option name="TYPE_CASE" value="2" />
590591
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -628,7 +629,7 @@
628629
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
629630
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
630631
</GroovyCodeStyleSettings>
631-
<H2CodeStyleSettings version="6">
632+
<H2CodeStyleSettings version="7">
632633
<option name="KEYWORD_CASE" value="2" />
633634
<option name="TYPE_CASE" value="2" />
634635
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -668,7 +669,7 @@
668669
<option name="EXPR_CASE_THEN_WRAP" value="true" />
669670
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
670671
</H2CodeStyleSettings>
671-
<HSQLCodeStyleSettings version="6">
672+
<HSQLCodeStyleSettings version="7">
672673
<option name="KEYWORD_CASE" value="2" />
673674
<option name="TYPE_CASE" value="2" />
674675
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -732,7 +733,7 @@
732733
<option name="JD_PRESERVE_LINE_FEEDS" value="true" />
733734
<option name="JD_INDENT_ON_CONTINUATION" value="true" />
734735
</JavaCodeStyleSettings>
735-
<MSSQLCodeStyleSettings version="6">
736+
<MSSQLCodeStyleSettings version="7">
736737
<option name="KEYWORD_CASE" value="2" />
737738
<option name="TYPE_CASE" value="2" />
738739
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -775,7 +776,7 @@
775776
<MarkdownNavigatorCodeStyleSettings>
776777
<option name="WRAP_ON_TYPING" value="0" />
777778
</MarkdownNavigatorCodeStyleSettings>
778-
<MySQLCodeStyleSettings version="6">
779+
<MySQLCodeStyleSettings version="7">
779780
<option name="KEYWORD_CASE" value="2" />
780781
<option name="TYPE_CASE" value="2" />
781782
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -815,7 +816,7 @@
815816
<option name="EXPR_CASE_THEN_WRAP" value="true" />
816817
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
817818
</MySQLCodeStyleSettings>
818-
<OracleCodeStyleSettings version="6">
819+
<OracleCodeStyleSettings version="7">
819820
<option name="KEYWORD_CASE" value="2" />
820821
<option name="TYPE_CASE" value="2" />
821822
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -861,7 +862,7 @@
861862
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
862863
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
863864
</PHPCodeStyleSettings>
864-
<PostgresCodeStyleSettings version="6">
865+
<PostgresCodeStyleSettings version="7">
865866
<option name="KEYWORD_CASE" value="2" />
866867
<option name="TYPE_CASE" value="2" />
867868
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -904,7 +905,7 @@
904905
<Properties>
905906
<option name="KEEP_BLANK_LINES" value="true" />
906907
</Properties>
907-
<SQLiteCodeStyleSettings version="6">
908+
<SQLiteCodeStyleSettings version="7">
908909
<option name="KEYWORD_CASE" value="2" />
909910
<option name="TYPE_CASE" value="2" />
910911
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -947,7 +948,7 @@
947948
<ScalaCodeStyleSettings>
948949
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
949950
</ScalaCodeStyleSettings>
950-
<SqlCodeStyleSettings version="6">
951+
<SqlCodeStyleSettings version="7">
951952
<option name="KEYWORD_CASE" value="2" />
952953
<option name="TYPE_CASE" value="2" />
953954
<option name="CUSTOM_TYPE_CASE" value="2" />
@@ -1012,7 +1013,7 @@
10121013
<option name="WRAP_PARENTHESIZED_EXPRESSION_INSIDE_VALUES" value="0" />
10131014
<option name="NEW_LINE_AFTER_SELECT_ITEM" value="false" />
10141015
</SqlCodeStyleSettings>
1015-
<SybaseCodeStyleSettings version="6">
1016+
<SybaseCodeStyleSettings version="7">
10161017
<option name="KEYWORD_CASE" value="2" />
10171018
<option name="TYPE_CASE" value="2" />
10181019
<option name="CUSTOM_TYPE_CASE" value="2" />

src/main/java/org/primeframework/mvc/action/DefaultActionMappingWorkflow.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2001-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import io.fusionauth.http.server.HTTPRequest;
2727
import io.fusionauth.http.server.HTTPResponse;
2828
import org.primeframework.mvc.NotAllowedException;
29+
import org.primeframework.mvc.config.MVCConfiguration;
2930
import org.primeframework.mvc.http.HTTPTools;
3031
import org.primeframework.mvc.http.Status;
3132
import org.primeframework.mvc.parameter.DefaultParameterParser;
@@ -47,6 +48,8 @@ public class DefaultActionMappingWorkflow implements ActionMappingWorkflow {
4748

4849
private final ActionMapper actionMapper;
4950

51+
private final MVCConfiguration configuration;
52+
5053
private final HTTPRequest request;
5154

5255
private final HTTPResponse response;
@@ -55,11 +58,12 @@ public class DefaultActionMappingWorkflow implements ActionMappingWorkflow {
5558

5659
@Inject
5760
public DefaultActionMappingWorkflow(HTTPRequest request, HTTPResponse response, ActionInvocationStore actionInvocationStore,
58-
ActionMapper actionMapper) {
61+
ActionMapper actionMapper, MVCConfiguration configuration) {
5962
this.request = request;
6063
this.response = response;
6164
this.actionInvocationStore = actionInvocationStore;
6265
this.actionMapper = actionMapper;
66+
this.configuration = configuration;
6367
}
6468

6569
/**
@@ -143,20 +147,24 @@ public void perform(WorkflowChain chain) throws IOException {
143147

144148
private String determineURI() {
145149
String uri = null;
146-
Set<String> keys = request.getParameters().keySet();
147-
for (String key : keys) {
148-
if (key.startsWith(DefaultParameterParser.ACTION_PREFIX)) {
149-
String actionParameterName = key.substring(4);
150-
String actionParameterValue = request.getParameter(key);
151-
if (request.getParameter(actionParameterName) != null && actionParameterValue.trim().length() > 0) {
152-
uri = actionParameterValue;
153-
154-
// Handle relative URIs
155-
if (!uri.startsWith("/")) {
156-
String requestURI = HTTPTools.getRequestURI(request);
157-
int index = requestURI.lastIndexOf('/');
158-
if (index >= 0) {
159-
uri = requestURI.substring(0, index) + "/" + uri;
150+
151+
if (configuration.allowActionParameterDuringActionMappingWorkflow()) {
152+
Set<String> keys = request.getParameters().keySet();
153+
for (String key : keys) {
154+
if (key.startsWith(DefaultParameterParser.ACTION_PREFIX)) {
155+
156+
String actionParameterName = key.substring(4);
157+
String actionParameterValue = request.getParameter(key);
158+
if (request.getParameter(actionParameterName) != null && actionParameterValue.trim().length() > 0) {
159+
uri = actionParameterValue;
160+
161+
// Handle relative URIs
162+
if (!uri.startsWith("/")) {
163+
String requestURI = HTTPTools.getRequestURI(request);
164+
int index = requestURI.lastIndexOf('/');
165+
if (index >= 0) {
166+
uri = requestURI.substring(0, index) + "/" + uri;
167+
}
160168
}
161169
}
162170
}

src/main/java/org/primeframework/mvc/config/AbstractMVCConfiguration.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2012-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,8 @@ public abstract class AbstractMVCConfiguration implements MVCConfiguration {
4040

4141
public static final long MAX_SIZE = 1024000;
4242

43+
public boolean allowActionParameterDuringActionMappingWorkflow = true;
44+
4345
public boolean autoHTMLEscapingEnabled = true;
4446

4547
public String controlTemplateDirectory = "control-templates";
@@ -76,6 +78,11 @@ public abstract class AbstractMVCConfiguration implements MVCConfiguration {
7678

7779
public List<Class<? extends Annotation>> unwrapAnnotations = Collections.singletonList(FieldUnwrapped.class);
7880

81+
@Override
82+
public boolean allowActionParameterDuringActionMappingWorkflow() {
83+
return allowActionParameterDuringActionMappingWorkflow;
84+
}
85+
7986
@Override
8087
public boolean autoHTMLEscapingEnabled() {
8188
return autoHTMLEscapingEnabled;

src/main/java/org/primeframework/mvc/config/MVCConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2023, Inversoft Inc., All Rights Reserved
2+
* Copyright (c) 2012-2024, Inversoft Inc., All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.Set;
2323

2424
import io.fusionauth.http.Cookie.SameSite;
25+
import org.primeframework.mvc.parameter.DefaultParameterParser;
2526
import org.primeframework.mvc.parameter.el.ExpressionEvaluator;
2627

2728
/**
@@ -31,6 +32,13 @@
3132
* @author Brian Pontarelli
3233
*/
3334
public interface MVCConfiguration {
35+
/**
36+
* In most cases you should disable this feature. While it may be useful, modifying the URI may have un-intended consequences.
37+
*
38+
* @return true if alternate actions can be specified by using the {@link DefaultParameterParser#ACTION_PREFIX} prefix.
39+
*/
40+
boolean allowActionParameterDuringActionMappingWorkflow();
41+
3442
/**
3543
* @return true if unknown parameters should be allowed, false if they are not allowed.
3644
*/
@@ -135,7 +143,7 @@ public interface MVCConfiguration {
135143

136144
/**
137145
* @return The number of seconds to check for Freemarker template updates (max integer means never and 0 means
138-
* always).
146+
* always).
139147
*/
140148
int templateCheckSeconds();
141149

@@ -146,7 +154,7 @@ public interface MVCConfiguration {
146154

147155
/**
148156
* @return The annotations that identify a field to be un-wrapped - or be considered transparent by the
149-
* {@link ExpressionEvaluator}.
157+
* {@link ExpressionEvaluator}.
150158
*/
151159
List<Class<? extends Annotation>> unwrapAnnotations();
152160
}

src/test/java/org/primeframework/mvc/GlobalTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import static org.testng.Assert.assertEquals;
7878
import static org.testng.Assert.assertSame;
7979
import static org.testng.Assert.assertTrue;
80+
import static org.testng.AssertJUnit.assertFalse;
8081
import static org.testng.FileAssert.fail;
8182

8283
/**
@@ -1176,6 +1177,31 @@ public void get_unknownParameters() throws Exception {
11761177
));
11771178
}
11781179

1180+
@Test
1181+
public void get_url_rewrite() {
1182+
simulator.test("/doesNotExist?__a_foo=/user/edit&foo=true")
1183+
.get()
1184+
.assertStatusCode(200)
1185+
.assertContainsNoFieldMessages()
1186+
.assertBodyContains("""
1187+
<head><title>Edit a user</title></head>
1188+
""");
1189+
assertTrue(EditAction.getCalled);
1190+
1191+
// Disabled
1192+
configuration.allowActionParameterDuringActionMappingWorkflow = false;
1193+
1194+
// Reset
1195+
EditAction.getCalled = false;
1196+
1197+
simulator.test("/doesNotExist?__a_foo=/user/edit&foo=true")
1198+
.get()
1199+
.assertStatusCode(404)
1200+
.assertContainsNoFieldMessages()
1201+
.assertBodyContains("The page is missing!");
1202+
assertFalse(EditAction.getCalled);
1203+
}
1204+
11791205
@Test
11801206
public void get_wellKnownDotPrefixed() throws Exception {
11811207
test.simulate(() -> simulator.test("/.well-known/openid-configuration")

src/test/java/org/primeframework/mvc/PrimeBaseTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,19 @@ public void beforeMethod() {
202202
// Reset allowUnknownParameters
203203
configuration.allowUnknownParameters = false;
204204

205+
// Reset to default
206+
configuration.allowActionParameterDuringActionMappingWorkflow = true;
207+
205208
// Reset the call count on the invocation finalizer
206209
MockMVCWorkflowFinalizer.Called.set(0);
207210

208211
// Reset accumulating logger, using TRACE for now to debug some tests
209212
((TestAccumulatingLogger) TestAccumulatingLoggerFactory.FACTORY.getLogger(PrimeBaseTest.class)).reset();
210213
TestAccumulatingLoggerFactory.FACTORY.getLogger(PrimeBaseTest.class).setLevel(Level.Trace);
211214

215+
// Reset
216+
EditAction.getCalled = false;
217+
212218
TestUnhandledExceptionHandler.reset();
213219
}
214220

src/test/java/org/primeframework/mvc/action/DefaultActionMappingWorkflowTest.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,25 @@ public void differentButtonClick() throws Exception {
5252
run("/admin/user/cancel", "/admin/user/cancel", null);
5353
}
5454

55+
@Test
56+
public void differentButtonClick_notAllowed() throws Exception {
57+
// Default behavior will be to limit using alternate form actions
58+
configuration.allowActionParameterDuringActionMappingWorkflow = false;
59+
60+
request.setPath("/admin/user/edit");
61+
request.setMethod(HTTPMethod.POST);
62+
request.addURLParameter("__a_submit", "");
63+
request.addURLParameter("__a_cancel", "/admin/user/cancel");
64+
request.addURLParameter("cancel", "Cancel");
65+
66+
run("/admin/user/edit", "/admin/user/edit", null);
67+
}
68+
5569
@Test
5670
public void differentButtonClickRelativeURI() throws Exception {
71+
// enable alternate form actions
72+
configuration.allowActionParameterDuringActionMappingWorkflow = true;
73+
5774
request.setPath("/admin/user/edit");
5875
request.setMethod(HTTPMethod.POST);
5976
request.addURLParameter("__a_submit", "");
@@ -126,7 +143,7 @@ private void run(String fullURI, String uri, String extension) throws Exception
126143
chain.continueWorkflow();
127144
EasyMock.replay(chain);
128145

129-
DefaultActionMappingWorkflow workflow = new DefaultActionMappingWorkflow(request, response, store, new DefaultActionMapper(provider, injector));
146+
DefaultActionMappingWorkflow workflow = new DefaultActionMappingWorkflow(request, response, store, new DefaultActionMapper(provider, injector), configuration);
130147
workflow.perform(chain);
131148

132149
ActionInvocation ai = capture.getValue();

0 commit comments

Comments
 (0)