Skip to content

Commit 163740c

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 0c86c1d commit 163740c

File tree

8 files changed

+236
-101
lines changed

8 files changed

+236
-101
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);
@@ -52,7 +52,6 @@ public static Expression create(final String value) {
5252

5353
Entry<Pattern, Class<? extends Expression>> matchedExpression = matchedExpressionEntry.get();
5454
Pattern expressionPattern = matchedExpression.getKey();
55-
Class<? extends Expression> expressionType = matchedExpression.getValue();
5655

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

70-
return new SimpleExpression(variableName, variablePattern);
69+
return new SimpleExpression(variableName, variablePattern, type);
7170
}
7271

7372
private static String stripBraces(String expression) {
@@ -80,36 +79,21 @@ private static String stripBraces(String expression) {
8079
return expression;
8180
}
8281

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

107-
SimpleExpression(String expression, String pattern) {
88+
private final FragmentType type;
89+
90+
SimpleExpression(String expression, String pattern, FragmentType type) {
10891
super(expression, pattern);
92+
this.type = type;
10993
}
11094

11195
String encode(Object value) {
112-
return UriUtils.encode(value.toString(), Util.UTF_8);
96+
return UriUtils.encodeReserved(value.toString(), type, Util.UTF_8);
11397
}
11498

11599
@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;
@@ -83,9 +84,9 @@ public String expand(Map<String, ?> variables) {
8384
Object value = variables.get(expression.getName());
8485
if (value != null) {
8586
String expanded = expression.expand(value, this.encode.isEncodingRequired());
86-
if (!this.encodeSlash) {
87+
if (this.encodeSlash) {
8788
logger.fine("Explicit slash decoding specified, decoding all slashes in uri");
88-
expanded = expanded.replaceAll("\\%2F", "/");
89+
expanded = expanded.replaceAll("/", "%2F");
8990
}
9091
resolved.append(expanded);
9192
} else {
@@ -202,7 +203,9 @@ private void parseFragment(String fragment, boolean query) {
202203

203204
if (chunk.startsWith("{")) {
204205
/* it's an expression, defer encoding until resolution */
205-
Expression expression = Expressions.create(chunk);
206+
FragmentType type = (query) ? FragmentType.QUERY : FragmentType.PATH_SEGMENT;
207+
208+
Expression expression = Expressions.create(chunk, type);
206209
if (expression == null) {
207210
this.templateChunks.add(Literal.create(encode(chunk, query)));
208211
} else {

0 commit comments

Comments
 (0)