1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.jsr356.server.pathmap;
20
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.eclipse.jetty.util.TypeUtil;
33 import org.eclipse.jetty.util.log.Log;
34 import org.eclipse.jetty.util.log.Logger;
35 import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
36 import org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec;
37
38
39
40
41
42
43
44 public class WebSocketPathSpec extends RegexPathSpec
45 {
46 private static final Logger LOG = Log.getLogger(WebSocketPathSpec.class);
47
48 private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
49
50 private static final String VARIABLE_RESERVED = ":/?#[]@" +
51 "!$&'()*+,;=";
52
53 private static final String VARIABLE_SYMBOLS="-._";
54 private static final Set<String> FORBIDDEN_SEGMENTS;
55
56 static
57 {
58 FORBIDDEN_SEGMENTS = new HashSet<>();
59 FORBIDDEN_SEGMENTS.add("/./");
60 FORBIDDEN_SEGMENTS.add("/../");
61 FORBIDDEN_SEGMENTS.add("//");
62 }
63
64 private String variables[];
65
66 public WebSocketPathSpec(String pathParamSpec)
67 {
68 super();
69 Objects.requireNonNull(pathParamSpec,"Path Param Spec cannot be null");
70
71 if ("".equals(pathParamSpec) || "/".equals(pathParamSpec))
72 {
73 super.pathSpec = "/";
74 super.pattern = Pattern.compile("^/$");
75 super.pathDepth = 1;
76 this.specLength = 1;
77 this.variables = new String[0];
78 this.group = PathSpecGroup.EXACT;
79 return;
80 }
81
82 if (pathParamSpec.charAt(0) != '/')
83 {
84
85 StringBuilder err = new StringBuilder();
86 err.append("Syntax Error: path spec \"");
87 err.append(pathParamSpec);
88 err.append("\" must start with '/'");
89 throw new IllegalArgumentException(err.toString());
90 }
91
92 for (String forbidden : FORBIDDEN_SEGMENTS)
93 {
94 if (pathParamSpec.contains(forbidden))
95 {
96 StringBuilder err = new StringBuilder();
97 err.append("Syntax Error: segment ");
98 err.append(forbidden);
99 err.append(" is forbidden in path spec: ");
100 err.append(pathParamSpec);
101 throw new IllegalArgumentException(err.toString());
102 }
103 }
104
105 this.pathSpec = pathParamSpec;
106
107 StringBuilder regex = new StringBuilder();
108 regex.append('^');
109
110 List<String> varNames = new ArrayList<>();
111
112 String segments[] = pathParamSpec.substring(1).split("/");
113 char segmentSignature[] = new char[segments.length];
114 this.pathDepth = segments.length;
115 for (int i = 0; i < segments.length; i++)
116 {
117 String segment = segments[i];
118 Matcher mat = VARIABLE_PATTERN.matcher(segment);
119
120 if (mat.matches())
121 {
122
123 String variable = mat.group(1);
124 if (varNames.contains(variable))
125 {
126
127 StringBuilder err = new StringBuilder();
128 err.append("Syntax Error: variable ");
129 err.append(variable);
130 err.append(" is duplicated in path spec: ");
131 err.append(pathParamSpec);
132 throw new IllegalArgumentException(err.toString());
133 }
134
135 assertIsValidVariableLiteral(variable);
136
137 segmentSignature[i] = 'v';
138
139 varNames.add(variable);
140
141 regex.append("/([^/]+)");
142 }
143 else if (mat.find(0))
144 {
145
146 StringBuilder err = new StringBuilder();
147 err.append("Syntax Error: variable ");
148 err.append(mat.group());
149 err.append(" must exist as entire path segment: ");
150 err.append(pathParamSpec);
151 throw new IllegalArgumentException(err.toString());
152 }
153 else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
154 {
155
156 StringBuilder err = new StringBuilder();
157 err.append("Syntax Error: invalid path segment /");
158 err.append(segment);
159 err.append("/ variable declaration incomplete: ");
160 err.append(pathParamSpec);
161 throw new IllegalArgumentException(err.toString());
162 }
163 else if (segment.indexOf('*') >= 0)
164 {
165
166 StringBuilder err = new StringBuilder();
167 err.append("Syntax Error: path segment /");
168 err.append(segment);
169 err.append("/ contains a wildcard symbol (not supported by javax.websocket): ");
170 err.append(pathParamSpec);
171 throw new IllegalArgumentException(err.toString());
172 }
173 else
174 {
175
176 segmentSignature[i] = 'e';
177
178 regex.append('/');
179
180 for (char c : segment.toCharArray())
181 {
182 if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
183 {
184 regex.append('\\');
185 }
186 regex.append(c);
187 }
188 }
189 }
190
191
192 if(pathParamSpec.charAt(pathParamSpec.length()-1) == '/')
193 {
194 regex.append('/');
195 }
196
197 regex.append('$');
198
199 this.pattern = Pattern.compile(regex.toString());
200
201 int varcount = varNames.size();
202 this.variables = varNames.toArray(new String[varcount]);
203
204
205 String sig = String.valueOf(segmentSignature);
206
207 if (Pattern.matches("^e*$",sig))
208 {
209 this.group = PathSpecGroup.EXACT;
210 }
211 else if (Pattern.matches("^e*v+",sig))
212 {
213 this.group = PathSpecGroup.PREFIX_GLOB;
214 }
215 else if (Pattern.matches("^v+e+",sig))
216 {
217 this.group = PathSpecGroup.SUFFIX_GLOB;
218 }
219 else
220 {
221 this.group = PathSpecGroup.MIDDLE_GLOB;
222 }
223 }
224
225
226
227
228
229
230 private void assertIsValidVariableLiteral(String variable)
231 {
232 int len = variable.length();
233
234 int i = 0;
235 int codepoint;
236 boolean valid = (len > 0);
237
238 while (valid && i < len)
239 {
240 codepoint = variable.codePointAt(i);
241 i += Character.charCount(codepoint);
242
243
244 if (isValidBasicLiteralCodepoint(codepoint))
245 {
246 continue;
247 }
248
249
250 if (Character.isSupplementaryCodePoint(codepoint))
251 {
252 continue;
253 }
254
255
256 if (codepoint == '%')
257 {
258 if (i + 2 > len)
259 {
260
261 valid = false;
262 continue;
263 }
264 codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
265 codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
266
267
268 if (isValidBasicLiteralCodepoint(codepoint))
269 {
270 continue;
271 }
272 }
273
274 valid = false;
275 }
276
277 if (!valid)
278 {
279
280 StringBuilder err = new StringBuilder();
281 err.append("Syntax Error: variable {");
282 err.append(variable);
283 err.append("} an invalid variable name: ");
284 err.append(pathSpec);
285 throw new IllegalArgumentException(err.toString());
286 }
287 }
288
289 private boolean isValidBasicLiteralCodepoint(int codepoint)
290 {
291
292 if((codepoint >= 'a' && codepoint <= 'z') ||
293 (codepoint >= 'A' && codepoint <= 'Z') ||
294 (codepoint >= '0' && codepoint <= '9'))
295 {
296 return true;
297 }
298
299
300 if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
301 {
302 return true;
303 }
304
305
306 if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
307 {
308 LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
309 return false;
310 }
311
312 return false;
313 }
314
315 public Map<String, String> getPathParams(String path)
316 {
317 Matcher matcher = getMatcher(path);
318 if (matcher.matches())
319 {
320 if (group == PathSpecGroup.EXACT)
321 {
322 return Collections.emptyMap();
323 }
324 Map<String, String> ret = new HashMap<>();
325 int groupCount = matcher.groupCount();
326 for (int i = 1; i <= groupCount; i++)
327 {
328 ret.put(this.variables[i - 1],matcher.group(i));
329 }
330 return ret;
331 }
332 return null;
333 }
334
335 public int getVariableCount()
336 {
337 return variables.length;
338 }
339
340 public String[] getVariables()
341 {
342 return this.variables;
343 }
344 }