Skip to content

Commit 5d12416

Browse files
authored
Adding URI segment specific encoding (#882)
* Adding URI segment specific encoding Fixes #879 URI encoding introduced in Feign 10.x was refactored to be more in line with URI and URI Template specifications respectively. One change was to ensure that certain reserved characters were not encoded incorrectly. The result was that path segment specific reserved characters were being preserved on the query string as well. This change updates the `UriTemplate` and `Expression` classes to recognize the segment of the URI that is being processed and apply the segment specific encoding correctly. One important change regarding the `+` sign. Per the URI specification, a `+` sign is allowed in both the path and query segments of a URI, however, handling of the symbol on the query can be inconsistent. In some legacy systems, the `+` is equivalent to the a space. Feign takes the approach of modern systems, where a `+` symbol should not reprsent a space and is explicitly encoded as `%2B` when found on a query string. If you wish to use `+` as a space, then use the literal ` ` character or encode the value directly as `%20`
1 parent 03aabcf commit 5d12416

File tree

8 files changed

+227
-102
lines changed

8 files changed

+227
-102
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ See [Advanced Usage](#advanced-usage) for more examples.
113113
>
114114
> `@RequestLine` and `@QueryMap` templates do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` property on the `@RequestLine` to `false`.
115115
116+
> **What about plus? `+`**
117+
>
118+
> Per the URI specification, a `+` sign is allowed in both the path and query segments of a URI, however, handling of
119+
> the symbol on the query can be inconsistent. In some legacy systems, the `+` is equivalent to the a space. Feign takes the approach of modern systems, where a
120+
> `+` symbol should not represent a space and is explicitly encoded as `%2B` when found on a query string.
121+
>
122+
> If you wish to use `+` as a space, then use the literal ` ` character or encode the value directly as `%20`
123+
116124
##### Custom Expansion
117125

118126
The `@Param` annotation has an optional property `expander` allowing for complete control over the individual parameter's expansion.

core/src/main/java/feign/template/Expressions.java

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package feign.template;
1515

1616
import feign.Util;
17+
import feign.template.UriUtils.FragmentType;
1718
import java.util.ArrayList;
1819
import java.util.LinkedHashMap;
1920
import java.util.List;
@@ -28,11 +29,10 @@ public final class Expressions {
2829

2930
static {
3031
expressions = new LinkedHashMap<>();
31-
expressions.put(Pattern.compile("\\+(\\w[-\\w.]*[ ]*)(:(.+))?"), ReservedExpression.class);
3232
expressions.put(Pattern.compile("(\\w[-\\w.]*[ ]*)(:(.+))?"), SimpleExpression.class);
3333
}
3434

35-
public static Expression create(final String value) {
35+
public static Expression create(final String value, final FragmentType type) {
3636

3737
/* remove the start and end braces */
3838
final String expression = stripBraces(value);
@@ -53,7 +53,6 @@ public static Expression create(final String value) {
5353

5454
Entry<Pattern, Class<? extends Expression>> matchedExpression = matchedExpressionEntry.get();
5555
Pattern expressionPattern = matchedExpression.getKey();
56-
Class<? extends Expression> expressionType = matchedExpression.getValue();
5756

5857
/* create a new regular expression matcher for the expression */
5958
String variableName = null;
@@ -68,7 +67,7 @@ public static Expression create(final String value) {
6867
}
6968
}
7069

71-
return new SimpleExpression(variableName, variablePattern);
70+
return new SimpleExpression(variableName, variablePattern, type);
7271
}
7372

7473
private static String stripBraces(String expression) {
@@ -81,36 +80,21 @@ private static String stripBraces(String expression) {
8180
return expression;
8281
}
8382

84-
/**
85-
* Expression that does not encode reserved characters. This expression adheres to RFC 6570
86-
* <a href="https://tools.ietf.org/html/rfc6570#section-3.2.3">Reserved Expansion (Level 2)</a>
87-
* specification.
88-
*/
89-
public static class ReservedExpression extends SimpleExpression {
90-
private final String RESERVED_CHARACTERS = ":/?#[]@!$&\'()*+,;=";
91-
92-
ReservedExpression(String expression, String pattern) {
93-
super(expression, pattern);
94-
}
95-
96-
@Override
97-
String encode(Object value) {
98-
return UriUtils.encodeReserved(value.toString(), RESERVED_CHARACTERS, Util.UTF_8);
99-
}
100-
}
101-
10283
/**
10384
* Expression that adheres to Simple String Expansion as outlined in <a
10485
* href="https://tools.ietf.org/html/rfc6570#section-3.2.2>Simple String Expansion (Level 1)</a>
10586
*/
10687
static class SimpleExpression extends Expression {
10788

108-
SimpleExpression(String expression, String pattern) {
89+
private final FragmentType type;
90+
91+
SimpleExpression(String expression, String pattern, FragmentType type) {
10992
super(expression, pattern);
93+
this.type = type;
11094
}
11195

11296
String encode(Object value) {
113-
return UriUtils.encode(value.toString(), Util.UTF_8);
97+
return UriUtils.encodeReserved(value.toString(), type, Util.UTF_8);
11498
}
11599

116100
@Override

core/src/main/java/feign/template/Template.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package feign.template;
1515

16+
import feign.template.UriUtils.FragmentType;
1617
import java.nio.charset.Charset;
1718
import java.util.ArrayList;
1819
import java.util.List;
@@ -80,9 +81,9 @@ public String expand(Map<String, ?> variables) {
8081
Object value = variables.get(expression.getName());
8182
if (value != null) {
8283
String expanded = expression.expand(value, this.encode.isEncodingRequired());
83-
if (!this.encodeSlash) {
84+
if (this.encodeSlash) {
8485
logger.fine("Explicit slash decoding specified, decoding all slashes in uri");
85-
expanded = expanded.replaceAll("\\%2F", "/");
86+
expanded = expanded.replaceAll("/", "%2F");
8687
}
8788
resolved.append(expanded);
8889
} else {
@@ -200,7 +201,9 @@ private void parseFragment(String fragment, boolean query) {
200201

201202
if (chunk.startsWith("{")) {
202203
/* it's an expression, defer encoding until resolution */
203-
Expression expression = Expressions.create(chunk);
204+
FragmentType type = (query) ? FragmentType.QUERY : FragmentType.PATH_SEGMENT;
205+
206+
Expression expression = Expressions.create(chunk, type);
204207
if (expression == null) {
205208
this.templateChunks.add(Literal.create(encode(chunk, query)));
206209
} else {

0 commit comments

Comments
 (0)