]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java
updating jetty to jetty-9.2.16.v2016040
[gigi.git] / lib / jetty / org / eclipse / jetty / http / pathmap / UriTemplatePathSpec.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19 package org.eclipse.jetty.http.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
36 /**
37  * PathSpec for URI Template based declarations
38  * 
39  * @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
40  */
41 public class UriTemplatePathSpec extends RegexPathSpec
42 {
43     private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);
44     
45     private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
46     /** Reserved Symbols in URI Template variable */
47     private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
48                                                     "!$&'()*+,;="; // sub-delims
49     /** Allowed Symbols in a URI Template variable */
50     private static final String VARIABLE_SYMBOLS="-._";
51     private static final Set<String> FORBIDDEN_SEGMENTS;
52
53     static
54     {
55         FORBIDDEN_SEGMENTS = new HashSet<>();
56         FORBIDDEN_SEGMENTS.add("/./");
57         FORBIDDEN_SEGMENTS.add("/../");
58         FORBIDDEN_SEGMENTS.add("//");
59     }
60
61     private String variables[];
62
63     public UriTemplatePathSpec(String rawSpec)
64     {
65         super();
66         Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null");
67
68         if ("".equals(rawSpec) || "/".equals(rawSpec))
69         {
70             super.pathSpec = "/";
71             super.pattern = Pattern.compile("^/$");
72             super.pathDepth = 1;
73             this.specLength = 1;
74             this.variables = new String[0];
75             this.group = PathSpecGroup.EXACT;
76             return;
77         }
78
79         if (rawSpec.charAt(0) != '/')
80         {
81             // path specs must start with '/'
82             StringBuilder err = new StringBuilder();
83             err.append("Syntax Error: path spec \"");
84             err.append(rawSpec);
85             err.append("\" must start with '/'");
86             throw new IllegalArgumentException(err.toString());
87         }
88
89         for (String forbidden : FORBIDDEN_SEGMENTS)
90         {
91             if (rawSpec.contains(forbidden))
92             {
93                 StringBuilder err = new StringBuilder();
94                 err.append("Syntax Error: segment ");
95                 err.append(forbidden);
96                 err.append(" is forbidden in path spec: ");
97                 err.append(rawSpec);
98                 throw new IllegalArgumentException(err.toString());
99             }
100         }
101
102         this.pathSpec = rawSpec;
103
104         StringBuilder regex = new StringBuilder();
105         regex.append('^');
106
107         List<String> varNames = new ArrayList<>();
108         // split up into path segments (ignoring the first slash that will always be empty)
109         String segments[] = rawSpec.substring(1).split("/");
110         char segmentSignature[] = new char[segments.length];
111         this.pathDepth = segments.length;
112         for (int i = 0; i < segments.length; i++)
113         {
114             String segment = segments[i];
115             Matcher mat = VARIABLE_PATTERN.matcher(segment);
116
117             if (mat.matches())
118             {
119                 // entire path segment is a variable.
120                 String variable = mat.group(1);
121                 if (varNames.contains(variable))
122                 {
123                     // duplicate variable names
124                     StringBuilder err = new StringBuilder();
125                     err.append("Syntax Error: variable ");
126                     err.append(variable);
127                     err.append(" is duplicated in path spec: ");
128                     err.append(rawSpec);
129                     throw new IllegalArgumentException(err.toString());
130                 }
131
132                 assertIsValidVariableLiteral(variable);
133
134                 segmentSignature[i] = 'v'; // variable
135                 // valid variable name
136                 varNames.add(variable);
137                 // build regex
138                 regex.append("/([^/]+)");
139             }
140             else if (mat.find(0))
141             {
142                 // variable exists as partial segment
143                 StringBuilder err = new StringBuilder();
144                 err.append("Syntax Error: variable ");
145                 err.append(mat.group());
146                 err.append(" must exist as entire path segment: ");
147                 err.append(rawSpec);
148                 throw new IllegalArgumentException(err.toString());
149             }
150             else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
151             {
152                 // variable is split with a path separator
153                 StringBuilder err = new StringBuilder();
154                 err.append("Syntax Error: invalid path segment /");
155                 err.append(segment);
156                 err.append("/ variable declaration incomplete: ");
157                 err.append(rawSpec);
158                 throw new IllegalArgumentException(err.toString());
159             }
160             else if (segment.indexOf('*') >= 0)
161             {
162                 // glob segment
163                 StringBuilder err = new StringBuilder();
164                 err.append("Syntax Error: path segment /");
165                 err.append(segment);
166                 err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): ");
167                 err.append(rawSpec);
168                 throw new IllegalArgumentException(err.toString());
169             }
170             else
171             {
172                 // valid path segment
173                 segmentSignature[i] = 'e'; // exact
174                 // build regex
175                 regex.append('/');
176                 // escape regex special characters
177                 for (char c : segment.toCharArray())
178                 {
179                     if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
180                     {
181                         regex.append('\\');
182                     }
183                     regex.append(c);
184                 }
185             }
186         }
187         
188         // Handle trailing slash (which is not picked up during split)
189         if(rawSpec.charAt(rawSpec.length()-1) == '/')
190         {
191             regex.append('/');
192         }
193
194         regex.append('$');
195
196         this.pattern = Pattern.compile(regex.toString());
197
198         int varcount = varNames.size();
199         this.variables = varNames.toArray(new String[varcount]);
200
201         // Convert signature to group
202         String sig = String.valueOf(segmentSignature);
203
204         if (Pattern.matches("^e*$",sig))
205         {
206             this.group = PathSpecGroup.EXACT;
207         }
208         else if (Pattern.matches("^e*v+",sig))
209         {
210             this.group = PathSpecGroup.PREFIX_GLOB;
211         }
212         else if (Pattern.matches("^v+e+",sig))
213         {
214             this.group = PathSpecGroup.SUFFIX_GLOB;
215         }
216         else
217         {
218             this.group = PathSpecGroup.MIDDLE_GLOB;
219         }
220     }
221
222     /**
223      * Validate variable literal name, per RFC6570, Section 2.1 Literals
224      * @param variable
225      * @param pathParamSpec
226      */
227     private void assertIsValidVariableLiteral(String variable)
228     {
229         int len = variable.length();
230         
231         int i = 0;
232         int codepoint;
233         boolean valid = (len > 0); // must not be zero length
234         
235         while (valid && i < len)
236         {
237             codepoint = variable.codePointAt(i);
238             i += Character.charCount(codepoint);
239
240             // basic letters, digits, or symbols
241             if (isValidBasicLiteralCodepoint(codepoint))
242             {
243                 continue;
244             }
245
246             // The ucschar and iprivate pieces
247             if (Character.isSupplementaryCodePoint(codepoint))
248             {
249                 continue;
250             }
251
252             // pct-encoded
253             if (codepoint == '%')
254             {
255                 if (i + 2 > len)
256                 {
257                     // invalid percent encoding, missing extra 2 chars
258                     valid = false;
259                     continue;
260                 }
261                 codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
262                 codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
263
264                 // validate basic literal
265                 if (isValidBasicLiteralCodepoint(codepoint))
266                 {
267                     continue;
268                 }
269             }
270             
271             valid = false;
272         }
273
274         if (!valid)
275         {
276             // invalid variable name
277             StringBuilder err = new StringBuilder();
278             err.append("Syntax Error: variable {");
279             err.append(variable);
280             err.append("} an invalid variable name: ");
281             err.append(pathSpec);
282             throw new IllegalArgumentException(err.toString());
283         }
284     }
285     
286     private boolean isValidBasicLiteralCodepoint(int codepoint)
287     {
288         // basic letters or digits
289         if((codepoint >= 'a' && codepoint <= 'z') ||
290            (codepoint >= 'A' && codepoint <= 'Z') ||
291            (codepoint >= '0' && codepoint <= '9'))
292         {
293             return true;
294         }
295         
296         // basic allowed symbols
297         if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
298         {
299             return true; // valid simple value
300         }
301         
302         // basic reserved symbols
303         if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
304         {
305             LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
306             return false; // valid simple value
307         }
308
309         return false;
310     }
311
312     public Map<String, String> getPathParams(String path)
313     {
314         Matcher matcher = getMatcher(path);
315         if (matcher.matches())
316         {
317             if (group == PathSpecGroup.EXACT)
318             {
319                 return Collections.emptyMap();
320             }
321             Map<String, String> ret = new HashMap<>();
322             int groupCount = matcher.groupCount();
323             for (int i = 1; i <= groupCount; i++)
324             {
325                 ret.put(this.variables[i - 1],matcher.group(i));
326             }
327             return ret;
328         }
329         return null;
330     }
331
332     public int getVariableCount()
333     {
334         return variables.length;
335     }
336
337     public String[] getVariables()
338     {
339         return this.variables;
340     }
341 }