20
20
import java .nio .charset .StandardCharsets ;
21
21
import java .util .ArrayList ;
22
22
import java .util .Collections ;
23
+ import java .util .HashMap ;
23
24
import java .util .List ;
25
+ import java .util .Map ;
24
26
import java .util .stream .Collectors ;
25
27
26
28
import org .springframework .lang .Nullable ;
38
40
*/
39
41
final class DefaultPathContainer implements PathContainer {
40
42
41
- private static final MultiValueMap <String , String > EMPTY_MAP = new LinkedMultiValueMap <>();
43
+ private static final MultiValueMap <String , String > EMPTY_PARAMS = new LinkedMultiValueMap <>();
42
44
43
45
private static final PathContainer EMPTY_PATH = new DefaultPathContainer ("" , Collections .emptyList ());
44
46
45
- private static final PathContainer .Separator SEPARATOR = () -> "/" ;
47
+ private static final Map <Character , DefaultSeparator > SEPARATORS = new HashMap <>(2 );
48
+
49
+ static {
50
+ SEPARATORS .put ('/' , new DefaultSeparator ('/' , "%2F" ));
51
+ SEPARATORS .put ('.' , new DefaultSeparator ('.' , "%2E" ));
52
+ }
46
53
47
54
48
55
private final String path ;
@@ -72,10 +79,10 @@ public boolean equals(@Nullable Object other) {
72
79
if (this == other ) {
73
80
return true ;
74
81
}
75
- if (other == null || getClass () != other . getClass ( )) {
82
+ if (!( other instanceof PathContainer )) {
76
83
return false ;
77
84
}
78
- return this . path . equals (((DefaultPathContainer ) other ).path );
85
+ return value (). equals (((PathContainer ) other ).value () );
79
86
}
80
87
81
88
@ Override
@@ -89,18 +96,19 @@ public String toString() {
89
96
}
90
97
91
98
92
- static PathContainer createFromUrlPath (String path , String separator ) {
99
+ static PathContainer createFromUrlPath (String path , Options options ) {
93
100
if (path .equals ("" )) {
94
101
return EMPTY_PATH ;
95
102
}
96
- if (separator .length () == 0 ) {
97
- throw new IllegalArgumentException ("separator should not be empty" );
103
+ char separator = options .separator ();
104
+ DefaultSeparator separatorElement = SEPARATORS .get (separator );
105
+ if (separatorElement == null ) {
106
+ throw new IllegalArgumentException ("Unexpected separator: '" + separator + "'" );
98
107
}
99
- Separator separatorElement = separator .equals (SEPARATOR .value ()) ? SEPARATOR : () -> separator ;
100
108
List <Element > elements = new ArrayList <>();
101
109
int begin ;
102
- if (path .length () > 0 && path .startsWith ( separator ) ) {
103
- begin = separator . length () ;
110
+ if (path .length () > 0 && path .charAt ( 0 ) == separator ) {
111
+ begin = 1 ;
104
112
elements .add (separatorElement );
105
113
}
106
114
else {
@@ -110,23 +118,25 @@ static PathContainer createFromUrlPath(String path, String separator) {
110
118
int end = path .indexOf (separator , begin );
111
119
String segment = (end != -1 ? path .substring (begin , end ) : path .substring (begin ));
112
120
if (!segment .equals ("" )) {
113
- elements .add (parsePathSegment (segment ));
121
+ elements .add (options .shouldDecodeAndParseSegments () ?
122
+ decodeAndParsePathSegment (segment ) :
123
+ new DefaultPathSegment (segment , separatorElement ));
114
124
}
115
125
if (end == -1 ) {
116
126
break ;
117
127
}
118
128
elements .add (separatorElement );
119
- begin = end + separator . length () ;
129
+ begin = end + 1 ;
120
130
}
121
131
return new DefaultPathContainer (path , elements );
122
132
}
123
133
124
- private static PathSegment parsePathSegment (String segment ) {
134
+ private static PathSegment decodeAndParsePathSegment (String segment ) {
125
135
Charset charset = StandardCharsets .UTF_8 ;
126
136
int index = segment .indexOf (';' );
127
137
if (index == -1 ) {
128
138
String valueToMatch = StringUtils .uriDecode (segment , charset );
129
- return new DefaultPathSegment (segment , valueToMatch , EMPTY_MAP );
139
+ return new DefaultPathSegment (segment , valueToMatch , EMPTY_PARAMS );
130
140
}
131
141
else {
132
142
String valueToMatch = StringUtils .uriDecode (segment .substring (0 , index ), charset );
@@ -192,6 +202,30 @@ static PathContainer subPath(PathContainer container, int fromIndex, int toIndex
192
202
}
193
203
194
204
205
+ private static class DefaultSeparator implements Separator {
206
+
207
+ private final String separator ;
208
+
209
+ private final String encodedSequence ;
210
+
211
+
212
+ DefaultSeparator (char separator , String encodedSequence ) {
213
+ this .separator = String .valueOf (separator );
214
+ this .encodedSequence = encodedSequence ;
215
+ }
216
+
217
+
218
+ @ Override
219
+ public String value () {
220
+ return this .separator ;
221
+ }
222
+
223
+ public String encodedSequence () {
224
+ return this .encodedSequence ;
225
+ }
226
+ }
227
+
228
+
195
229
private static class DefaultPathSegment implements PathSegment {
196
230
197
231
private final String value ;
@@ -202,14 +236,29 @@ private static class DefaultPathSegment implements PathSegment {
202
236
203
237
private final MultiValueMap <String , String > parameters ;
204
238
205
- public DefaultPathSegment (String value , String valueToMatch , MultiValueMap <String , String > params ) {
206
- Assert .isTrue (!value .contains ("/" ), () -> "Invalid path segment value: " + value );
239
+
240
+ /**
241
+ * Constructor for decoded and parsed segments.
242
+ */
243
+ DefaultPathSegment (String value , String valueToMatch , MultiValueMap <String , String > params ) {
207
244
this .value = value ;
208
245
this .valueToMatch = valueToMatch ;
209
246
this .valueToMatchAsChars = valueToMatch .toCharArray ();
210
247
this .parameters = CollectionUtils .unmodifiableMultiValueMap (params );
211
248
}
212
249
250
+ /**
251
+ * Constructor for segments without decoding and parsing.
252
+ */
253
+ DefaultPathSegment (String value , DefaultSeparator separator ) {
254
+ this .value = value ;
255
+ this .valueToMatch = value .contains (separator .encodedSequence ()) ?
256
+ value .replaceAll (separator .encodedSequence (), separator .value ()) : value ;
257
+ this .valueToMatchAsChars = this .valueToMatch .toCharArray ();
258
+ this .parameters = EMPTY_PARAMS ;
259
+ }
260
+
261
+
213
262
@ Override
214
263
public String value () {
215
264
return this .value ;
@@ -235,10 +284,10 @@ public boolean equals(@Nullable Object other) {
235
284
if (this == other ) {
236
285
return true ;
237
286
}
238
- if (other == null || getClass () != other . getClass ( )) {
287
+ if (!( other instanceof PathSegment )) {
239
288
return false ;
240
289
}
241
- return this . value .equals (((DefaultPathSegment ) other ).value );
290
+ return value () .equals (((PathSegment ) other ).value () );
242
291
}
243
292
244
293
@ Override
0 commit comments