Skip to content

Commit 54c7e3b

Browse files
predic8rrayst
authored andcommitted
feat(core): enhance template marker detection and URL evaluation in Target and CallInterceptor
- Replaced `evaluateExpressions` with `urlIsTemplate` for better semantics and clarity. - Improved `Target` and `CallInterceptor` to skip URL evaluation when no template markers are present. - Introduced stricter validation for configurations involving template markers and illegal characters. - Updated relevant tests to validate changes and added new test cases for URL template evaluation.
1 parent facb801 commit 54c7e3b

5 files changed

Lines changed: 115 additions & 42 deletions

File tree

core/src/main/java/com/predic8/membrane/core/interceptor/flow/CallInterceptor.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.predic8.membrane.core.interceptor.*;
2121
import com.predic8.membrane.core.interceptor.lang.*;
2222
import com.predic8.membrane.core.transport.http.*;
23+
import com.predic8.membrane.core.util.*;
2324
import org.slf4j.*;
2425

2526
import java.io.*;
@@ -32,6 +33,7 @@
3233
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*;
3334
import static com.predic8.membrane.core.interceptor.Outcome.*;
3435
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
36+
import static com.predic8.membrane.core.util.TemplateUtil.*;
3537
import static java.util.Collections.*;
3638

3739
/**
@@ -43,6 +45,11 @@ public class CallInterceptor extends AbstractExchangeExpressionInterceptor {
4345

4446
private static final Logger log = LoggerFactory.getLogger(CallInterceptor.class);
4547

48+
/**
49+
* If url contains template marker ${}, if not expression evaluation is skipped
50+
*/
51+
private boolean urlIsTemplate = false;
52+
4653
/**
4754
* These headers are filtered out from the response of a called resource
4855
* and are not added to the current message.
@@ -56,6 +63,21 @@ public class CallInterceptor extends AbstractExchangeExpressionInterceptor {
5663
@Override
5764
public void init() {
5865
super.init();
66+
67+
if (router.getConfiguration().getUriFactory().isAllowIllegalCharacters()) {
68+
throw new ConfigurationException("""
69+
URL Templating and Illegal URL Characters
70+
71+
Url templating expressions and enablement of illegal characters in URLs are mutually exclusive. Either disable
72+
illegal characters in the configuration (configuration/uriFactory/allowIllegalCharacters) or remove the
73+
templating expression %s from the URL in the call URL.
74+
""".formatted(exchangeExpression.getExpression()));
75+
}
76+
77+
// If there is no template marker ${ than do not try to evaluate url as expression
78+
if (containsTemplateMarker(exchangeExpression.getExpression())) {
79+
urlIsTemplate = true;
80+
}
5981
}
6082

6183
@Override
@@ -69,7 +91,7 @@ public Outcome handleResponse(Exchange exc) {
6991
}
7092

7193
private Outcome handleInternal(Exchange exc) {
72-
final String dest = exchangeExpression.evaluate(exc, REQUEST, String.class);
94+
var dest = computeDestinationUrl(exc);
7395
log.debug("Calling {}", dest);
7496

7597
final Exchange newExc = createNewExchange(dest, getNewRequest(exc));
@@ -107,6 +129,13 @@ private Outcome handleInternal(Exchange exc) {
107129
}
108130
}
109131

132+
private String computeDestinationUrl(Exchange exc) {
133+
if (urlIsTemplate) {
134+
return exchangeExpression.evaluate(exc, REQUEST, String.class);
135+
}
136+
return exchangeExpression.getExpression();
137+
}
138+
110139
private ProblemDetails createProblemDetails(String dest) {
111140
return internal(router.getConfiguration().isProduction(), "call")
112141
.title("Error performing callout.")

core/src/main/java/com/predic8/membrane/core/proxies/Target.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.predic8.membrane.core.router.*;
2525
import com.predic8.membrane.core.util.*;
2626
import com.predic8.membrane.core.util.text.*;
27-
import com.predic8.membrane.core.util.uri.*;
2827
import com.predic8.membrane.core.util.uri.EscapingUtil.*;
2928
import org.slf4j.*;
3029

@@ -64,9 +63,9 @@ public class Target implements XMLSupport {
6463
private Function<String, String> escapingFunction;
6564

6665
/**
67-
* If exchangeExpressions should be evaluated.
66+
* If url contains template marker ${}, if not expression evaluation is skipped
6867
*/
69-
private boolean evaluateExpressions = false;
68+
private boolean urlIsTemplate = false;
7069

7170
private boolean adjustHostHeader = true;
7271

@@ -75,7 +74,8 @@ public class Target implements XMLSupport {
7574

7675
private InterceptorAdapter adapter;
7776

78-
public Target() {}
77+
public Target() {
78+
}
7979

8080
public Target(String host) {
8181
setHost(host);
@@ -91,21 +91,24 @@ public void init(Router router) {
9191
if (!containsTemplateMarker(url))
9292
return;
9393

94-
adapter = new InterceptorAdapter(router, xmlConfig);
94+
adapter = new InterceptorAdapter(router, xmlConfig);
9595

9696
if (router.getConfiguration().getUriFactory().isAllowIllegalCharacters()) {
9797
log.warn("{}Url templates are disabled for security.{} Disable configuration/uriFactory/allowIllegalCharacters to enable them. Illegal characters in templates may lead to injection attacks.", TerminalColors.BRIGHT_RED(), RESET());
9898
throw new ConfigurationException("""
99-
URL Templating and Illegal URL Characters
100-
101-
Url templating expressions and enablement of illegal characters in URLs are mutually exclusive. Either disable
102-
illegal characters in the configuration (configuration/uriFactory/allowIllegalCharacters) or remove the
103-
templating expression %s from the target URL.
104-
""".formatted(url));
105-
} else {
106-
evaluateExpressions = true;
107-
escapingFunction = getEscapingFunction(escaping);
99+
URL Templating and Illegal URL Characters
100+
101+
Url templating expressions and enablement of illegal characters in URLs are mutually exclusive. Either disable
102+
illegal characters in the configuration (configuration/uriFactory/allowIllegalCharacters) or remove the
103+
templating expression %s from the target URL.
104+
""".formatted(url));
105+
}
106+
107+
// If there is no template marker ${ than do not try to evaluate url as expression
108+
if(containsTemplateMarker(url)) {
109+
urlIsTemplate = true;
108110
}
111+
escapingFunction = getEscapingFunction(escaping);
109112
}
110113

111114
public void applyModifications(Exchange exc, Router router) {
@@ -124,14 +127,9 @@ private List<String> computeDestinationExpressions(Exchange exc, Router router)
124127

125128
private String evaluateTemplate(Exchange exc, Router router, String url, InterceptorAdapter adapter) {
126129
// Only evaluate if the target url contains a template marker ${}
127-
if (!evaluateExpressions)
130+
if (!urlIsTemplate)
128131
return url;
129132

130-
// If the url does not contain ${ we do not have to evaluate the expression
131-
if (!containsTemplateMarker(url)) {
132-
return url;
133-
}
134-
135133
// Without caching 1_000_000 => 37s with ConcurrentHashMap as Cache => 34s
136134
// Cache is probably not worth the effort and complexity
137135
return TemplateExchangeExpression.newInstance(adapter,
@@ -145,8 +143,8 @@ public String getHost() {
145143
return host;
146144
}
147145

148-
public boolean isEvaluateExpressions() {
149-
return evaluateExpressions;
146+
public boolean isUrlIsTemplate() {
147+
return urlIsTemplate;
150148
}
151149

152150
/**

core/src/test/java/com/predic8/membrane/core/interceptor/HTTPClientInterceptorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ void illegalCharacterWithoutTemplate() {
165165
fail();
166166
return;
167167
}
168-
assertFalse(apiProxy.getTarget().isEvaluateExpressions());
168+
assertFalse(apiProxy.getTarget().isUrlIsTemplate());
169169
assertEquals(1, exc.getDestinations().size());
170170

171171
// The template should not be evaluated, cause illegal characters are allowed!

core/src/test/java/com/predic8/membrane/core/interceptor/flow/CallInterceptorTest.java

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,38 @@
1313
limitations under the License. */
1414
package com.predic8.membrane.core.interceptor.flow;
1515

16-
import com.predic8.membrane.core.exchange.Exchange;
17-
import com.predic8.membrane.core.http.Request;
18-
import com.predic8.membrane.core.http.Response;
19-
import org.junit.jupiter.api.BeforeAll;
20-
import org.junit.jupiter.api.Test;
16+
import com.predic8.membrane.core.exchange.*;
17+
import com.predic8.membrane.core.http.*;
18+
import com.predic8.membrane.core.interceptor.*;
19+
import com.predic8.membrane.core.interceptor.templating.*;
20+
import com.predic8.membrane.core.openapi.serviceproxy.*;
21+
import com.predic8.membrane.core.router.*;
22+
import com.predic8.membrane.core.util.*;
23+
import org.junit.jupiter.api.*;
2124

22-
import java.net.URISyntaxException;
25+
import java.io.*;
26+
import java.net.*;
2327

2428
import static com.predic8.membrane.core.http.Header.*;
25-
import static com.predic8.membrane.core.interceptor.flow.CallInterceptor.copyHeadersFromResponseToRequest;
26-
import static org.junit.jupiter.api.Assertions.assertEquals;
27-
import static org.junit.jupiter.api.Assertions.assertNull;
29+
import static com.predic8.membrane.core.http.Request.*;
30+
import static com.predic8.membrane.core.interceptor.flow.CallInterceptor.*;
31+
import static org.junit.jupiter.api.Assertions.*;
2832

2933
class CallInterceptorTest {
3034

31-
static Exchange exc;
35+
private Exchange exc;
36+
private Router router;
3237

33-
@BeforeAll
34-
static void beforeAll() throws URISyntaxException {
35-
exc = Request.get("/foo").buildExchange();
38+
@BeforeEach
39+
void setup() throws URISyntaxException {
40+
exc = get("/foo").buildExchange();
41+
exc.setProperty("a", "b");
42+
router = new DefaultRouter();
43+
}
44+
45+
@AfterEach
46+
void teardown() {
47+
router.stop();
3648
}
3749

3850
@Test
@@ -46,12 +58,47 @@ void filterHeaders() {
4658
copyHeadersFromResponseToRequest(exc, exc);
4759

4860
// preserve
49-
assertEquals("42",exc.getRequest().getHeader().getFirstValue("X-FOO"));
61+
var header = exc.getRequest().getHeader();
62+
assertEquals("42", header.getFirstValue("X-FOO"));
5063

5164
// take out
52-
assertNull(exc.getRequest().getHeader().getFirstValue(SERVER));
53-
assertNull(exc.getRequest().getHeader().getFirstValue(TRANSFER_ENCODING));
54-
assertNull(exc.getRequest().getHeader().getFirstValue(CONTENT_ENCODING));
65+
assertNull(header.getFirstValue(SERVER));
66+
assertNull(header.getFirstValue(TRANSFER_ENCODING));
67+
assertNull(header.getFirstValue(CONTENT_ENCODING));
68+
}
69+
70+
@Test
71+
void evaluateUrlTemplate() throws IOException {
72+
extracted("Path: /b");
5573
}
5674

75+
@Test
76+
void urlTemplateAndAllowIllegalCharactersInURL() {
77+
router.getConfiguration().getUriFactory().setAllowIllegalCharacters(true);
78+
assertThrows(ConfigurationException.class, () -> extracted("dummy"));
79+
}
80+
81+
private void extracted(String expected) throws IOException {
82+
var api = new APIProxy();
83+
api.setKey(new APIProxyKey(2000));
84+
api.getFlow().add(new AbstractInterceptor() {
85+
@Override
86+
public Outcome handleRequest(Exchange exc) {
87+
System.out.println(exc);
88+
return super.handleRequest(exc);
89+
}
90+
});
91+
api.getFlow().add(new TemplateInterceptor() {{
92+
setSrc("Path: ${path}");
93+
}});
94+
api.getFlow().add(new ReturnInterceptor());
95+
router.add(api);
96+
router.start();
97+
98+
var ci = new CallInterceptor();
99+
ci.setUrl("http://localhost:2000/${property.a}");
100+
ci.init(router);
101+
ci.handleRequest(exc);
102+
assertEquals(expected, exc.getRequest().getBodyAsStringDecoded());
103+
}
57104
}

distribution/examples/configuration/apis.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ configuration:
2222
uriFactory:
2323
# Allow non-RFC-compliant characters like {, } in URIs
2424
# Attention: This may lead to security vulnerabilities! Use with care!
25-
# - ${expression} in the URI will be evaluated
2625
allowIllegalCharacters: true
2726
autoEscapeBackslashes: true # Escape backslashes in incoming URIs (\\ -> %5C)
2827
---

0 commit comments

Comments
 (0)