Mercurial Hosting > luan
comparison src/org/eclipse/jetty/http/PathMap.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
801:6a21393191c1 | 802:3428c60d7cfc |
---|---|
1 // | |
2 // ======================================================================== | |
3 // Copyright (c) 1995-2014 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; | |
20 | |
21 import java.io.Externalizable; | |
22 import java.util.Collections; | |
23 import java.util.HashMap; | |
24 import java.util.List; | |
25 import java.util.Map; | |
26 import java.util.Set; | |
27 import java.util.StringTokenizer; | |
28 | |
29 import org.eclipse.jetty.util.LazyList; | |
30 import org.eclipse.jetty.util.StringMap; | |
31 import org.eclipse.jetty.util.URIUtil; | |
32 | |
33 /* ------------------------------------------------------------ */ | |
34 /** URI path map to Object. | |
35 * This mapping implements the path specification recommended | |
36 * in the 2.2 Servlet API. | |
37 * | |
38 * Path specifications can be of the following forms:<PRE> | |
39 * /foo/bar - an exact path specification. | |
40 * /foo/* - a prefix path specification (must end '/*'). | |
41 * *.ext - a suffix path specification. | |
42 * / - the default path specification. | |
43 * </PRE> | |
44 * Matching is performed in the following order <NL> | |
45 * <LI>Exact match. | |
46 * <LI>Longest prefix match. | |
47 * <LI>Longest suffix match. | |
48 * <LI>default. | |
49 * </NL> | |
50 * Multiple path specifications can be mapped by providing a list of | |
51 * specifications. By default this class uses characters ":," as path | |
52 * separators, unless configured differently by calling the static | |
53 * method @see PathMap#setPathSpecSeparators(String) | |
54 * <P> | |
55 * Special characters within paths such as '?� and ';' are not treated specially | |
56 * as it is assumed they would have been either encoded in the original URL or | |
57 * stripped from the path. | |
58 * <P> | |
59 * This class is not synchronized. If concurrent modifications are | |
60 * possible then it should be synchronized at a higher level. | |
61 * | |
62 * | |
63 */ | |
64 public class PathMap extends HashMap implements Externalizable | |
65 { | |
66 /* ------------------------------------------------------------ */ | |
67 private static String __pathSpecSeparators = ":,"; | |
68 | |
69 /* ------------------------------------------------------------ */ | |
70 /** Set the path spec separator. | |
71 * Multiple path specification may be included in a single string | |
72 * if they are separated by the characters set in this string. | |
73 * By default this class uses ":," characters as path separators. | |
74 * @param s separators | |
75 */ | |
76 public static void setPathSpecSeparators(String s) | |
77 { | |
78 __pathSpecSeparators=s; | |
79 } | |
80 | |
81 /* --------------------------------------------------------------- */ | |
82 final StringMap _prefixMap=new StringMap(); | |
83 final StringMap _suffixMap=new StringMap(); | |
84 final StringMap _exactMap=new StringMap(); | |
85 | |
86 List _defaultSingletonList=null; | |
87 Entry _prefixDefault=null; | |
88 Entry _default=null; | |
89 final Set _entrySet; | |
90 boolean _nodefault=false; | |
91 | |
92 /* --------------------------------------------------------------- */ | |
93 /** Construct empty PathMap. | |
94 */ | |
95 public PathMap() | |
96 { | |
97 super(11); | |
98 _entrySet=entrySet(); | |
99 } | |
100 | |
101 /* --------------------------------------------------------------- */ | |
102 /** Construct empty PathMap. | |
103 */ | |
104 public PathMap(boolean nodefault) | |
105 { | |
106 super(11); | |
107 _entrySet=entrySet(); | |
108 _nodefault=nodefault; | |
109 } | |
110 | |
111 /* --------------------------------------------------------------- */ | |
112 /** Construct empty PathMap. | |
113 */ | |
114 public PathMap(int capacity) | |
115 { | |
116 super (capacity); | |
117 _entrySet=entrySet(); | |
118 } | |
119 | |
120 /* --------------------------------------------------------------- */ | |
121 /** Construct from dictionary PathMap. | |
122 */ | |
123 public PathMap(Map m) | |
124 { | |
125 putAll(m); | |
126 _entrySet=entrySet(); | |
127 } | |
128 | |
129 /* ------------------------------------------------------------ */ | |
130 public void writeExternal(java.io.ObjectOutput out) | |
131 throws java.io.IOException | |
132 { | |
133 HashMap map = new HashMap(this); | |
134 out.writeObject(map); | |
135 } | |
136 | |
137 /* ------------------------------------------------------------ */ | |
138 public void readExternal(java.io.ObjectInput in) | |
139 throws java.io.IOException, ClassNotFoundException | |
140 { | |
141 HashMap map = (HashMap)in.readObject(); | |
142 this.putAll(map); | |
143 } | |
144 | |
145 /* --------------------------------------------------------------- */ | |
146 /** Add a single path match to the PathMap. | |
147 * @param pathSpec The path specification, or comma separated list of | |
148 * path specifications. | |
149 * @param object The object the path maps to | |
150 */ | |
151 @Override | |
152 public Object put(Object pathSpec, Object object) | |
153 { | |
154 String str = pathSpec.toString(); | |
155 if ("".equals(str.trim())) | |
156 { | |
157 Entry entry = new Entry("",object); | |
158 entry.setMapped(""); | |
159 _exactMap.put("", entry); | |
160 return super.put("", object); | |
161 } | |
162 | |
163 StringTokenizer tok = new StringTokenizer(str,__pathSpecSeparators); | |
164 Object old =null; | |
165 | |
166 while (tok.hasMoreTokens()) | |
167 { | |
168 String spec=tok.nextToken(); | |
169 | |
170 if (!spec.startsWith("/") && !spec.startsWith("*.")) | |
171 throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); | |
172 | |
173 old = super.put(spec,object); | |
174 | |
175 // Make entry that was just created. | |
176 Entry entry = new Entry(spec,object); | |
177 | |
178 if (entry.getKey().equals(spec)) | |
179 { | |
180 if (spec.equals("/*")) | |
181 _prefixDefault=entry; | |
182 else if (spec.endsWith("/*")) | |
183 { | |
184 String mapped=spec.substring(0,spec.length()-2); | |
185 entry.setMapped(mapped); | |
186 _prefixMap.put(mapped,entry); | |
187 _exactMap.put(mapped,entry); | |
188 _exactMap.put(spec.substring(0,spec.length()-1),entry); | |
189 } | |
190 else if (spec.startsWith("*.")) | |
191 _suffixMap.put(spec.substring(2),entry); | |
192 else if (spec.equals(URIUtil.SLASH)) | |
193 { | |
194 if (_nodefault) | |
195 _exactMap.put(spec,entry); | |
196 else | |
197 { | |
198 _default=entry; | |
199 _defaultSingletonList= | |
200 Collections.singletonList(_default); | |
201 } | |
202 } | |
203 else | |
204 { | |
205 entry.setMapped(spec); | |
206 _exactMap.put(spec,entry); | |
207 } | |
208 } | |
209 } | |
210 | |
211 return old; | |
212 } | |
213 | |
214 /* ------------------------------------------------------------ */ | |
215 /** Get object matched by the path. | |
216 * @param path the path. | |
217 * @return Best matched object or null. | |
218 */ | |
219 public Object match(String path) | |
220 { | |
221 Map.Entry entry = getMatch(path); | |
222 if (entry!=null) | |
223 return entry.getValue(); | |
224 return null; | |
225 } | |
226 | |
227 | |
228 /* --------------------------------------------------------------- */ | |
229 /** Get the entry mapped by the best specification. | |
230 * @param path the path. | |
231 * @return Map.Entry of the best matched or null. | |
232 */ | |
233 public Entry getMatch(String path) | |
234 { | |
235 Map.Entry entry=null; | |
236 | |
237 if (path==null) | |
238 return null; | |
239 | |
240 int l=path.length(); | |
241 | |
242 //special case | |
243 if (l == 1 && path.charAt(0)=='/') | |
244 { | |
245 entry = (Map.Entry)_exactMap.get(""); | |
246 if (entry != null) | |
247 return (Entry)entry; | |
248 } | |
249 | |
250 // try exact match | |
251 entry=_exactMap.getEntry(path,0,l); | |
252 if (entry!=null) | |
253 return (Entry) entry.getValue(); | |
254 | |
255 // prefix search | |
256 int i=l; | |
257 while((i=path.lastIndexOf('/',i-1))>=0) | |
258 { | |
259 entry=_prefixMap.getEntry(path,0,i); | |
260 if (entry!=null) | |
261 return (Entry) entry.getValue(); | |
262 } | |
263 | |
264 // Prefix Default | |
265 if (_prefixDefault!=null) | |
266 return _prefixDefault; | |
267 | |
268 // Extension search | |
269 i=0; | |
270 while ((i=path.indexOf('.',i+1))>0) | |
271 { | |
272 entry=_suffixMap.getEntry(path,i+1,l-i-1); | |
273 if (entry!=null) | |
274 return (Entry) entry.getValue(); | |
275 } | |
276 | |
277 // Default | |
278 return _default; | |
279 } | |
280 | |
281 /* --------------------------------------------------------------- */ | |
282 /** Get all entries matched by the path. | |
283 * Best match first. | |
284 * @param path Path to match | |
285 * @return LazyList of Map.Entry instances key=pathSpec | |
286 */ | |
287 public Object getLazyMatches(String path) | |
288 { | |
289 Map.Entry entry; | |
290 Object entries=null; | |
291 | |
292 if (path==null) | |
293 return LazyList.getList(entries); | |
294 | |
295 int l=path.length(); | |
296 | |
297 // try exact match | |
298 entry=_exactMap.getEntry(path,0,l); | |
299 if (entry!=null) | |
300 entries=LazyList.add(entries,entry.getValue()); | |
301 | |
302 // prefix search | |
303 int i=l-1; | |
304 while((i=path.lastIndexOf('/',i-1))>=0) | |
305 { | |
306 entry=_prefixMap.getEntry(path,0,i); | |
307 if (entry!=null) | |
308 entries=LazyList.add(entries,entry.getValue()); | |
309 } | |
310 | |
311 // Prefix Default | |
312 if (_prefixDefault!=null) | |
313 entries=LazyList.add(entries,_prefixDefault); | |
314 | |
315 // Extension search | |
316 i=0; | |
317 while ((i=path.indexOf('.',i+1))>0) | |
318 { | |
319 entry=_suffixMap.getEntry(path,i+1,l-i-1); | |
320 if (entry!=null) | |
321 entries=LazyList.add(entries,entry.getValue()); | |
322 } | |
323 | |
324 // Default | |
325 if (_default!=null) | |
326 { | |
327 // Optimization for just the default | |
328 if (entries==null) | |
329 return _defaultSingletonList; | |
330 | |
331 entries=LazyList.add(entries,_default); | |
332 } | |
333 | |
334 return entries; | |
335 } | |
336 | |
337 /* --------------------------------------------------------------- */ | |
338 /** Get all entries matched by the path. | |
339 * Best match first. | |
340 * @param path Path to match | |
341 * @return List of Map.Entry instances key=pathSpec | |
342 */ | |
343 public List getMatches(String path) | |
344 { | |
345 return LazyList.getList(getLazyMatches(path)); | |
346 } | |
347 | |
348 /* --------------------------------------------------------------- */ | |
349 /** Return whether the path matches any entries in the PathMap, | |
350 * excluding the default entry | |
351 * @param path Path to match | |
352 * @return Whether the PathMap contains any entries that match this | |
353 */ | |
354 public boolean containsMatch(String path) | |
355 { | |
356 Entry match = getMatch(path); | |
357 return match!=null && !match.equals(_default); | |
358 } | |
359 | |
360 /* --------------------------------------------------------------- */ | |
361 @Override | |
362 public Object remove(Object pathSpec) | |
363 { | |
364 if (pathSpec!=null) | |
365 { | |
366 String spec=(String) pathSpec; | |
367 if (spec.equals("/*")) | |
368 _prefixDefault=null; | |
369 else if (spec.endsWith("/*")) | |
370 { | |
371 _prefixMap.remove(spec.substring(0,spec.length()-2)); | |
372 _exactMap.remove(spec.substring(0,spec.length()-1)); | |
373 _exactMap.remove(spec.substring(0,spec.length()-2)); | |
374 } | |
375 else if (spec.startsWith("*.")) | |
376 _suffixMap.remove(spec.substring(2)); | |
377 else if (spec.equals(URIUtil.SLASH)) | |
378 { | |
379 _default=null; | |
380 _defaultSingletonList=null; | |
381 } | |
382 else | |
383 _exactMap.remove(spec); | |
384 } | |
385 return super.remove(pathSpec); | |
386 } | |
387 | |
388 /* --------------------------------------------------------------- */ | |
389 @Override | |
390 public void clear() | |
391 { | |
392 _exactMap.clear(); | |
393 _prefixMap.clear(); | |
394 _suffixMap.clear(); | |
395 _default=null; | |
396 _defaultSingletonList=null; | |
397 super.clear(); | |
398 } | |
399 | |
400 /* --------------------------------------------------------------- */ | |
401 /** | |
402 * @return true if match. | |
403 */ | |
404 public static boolean match(String pathSpec, String path) | |
405 throws IllegalArgumentException | |
406 { | |
407 return match(pathSpec, path, false); | |
408 } | |
409 | |
410 /* --------------------------------------------------------------- */ | |
411 /** | |
412 * @return true if match. | |
413 */ | |
414 public static boolean match(String pathSpec, String path, boolean noDefault) | |
415 throws IllegalArgumentException | |
416 { | |
417 if (pathSpec.length()==0) | |
418 return "/".equals(path); | |
419 | |
420 char c = pathSpec.charAt(0); | |
421 if (c=='/') | |
422 { | |
423 if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) | |
424 return true; | |
425 | |
426 if(isPathWildcardMatch(pathSpec, path)) | |
427 return true; | |
428 } | |
429 else if (c=='*') | |
430 return path.regionMatches(path.length()-pathSpec.length()+1, | |
431 pathSpec,1,pathSpec.length()-1); | |
432 return false; | |
433 } | |
434 | |
435 /* --------------------------------------------------------------- */ | |
436 private static boolean isPathWildcardMatch(String pathSpec, String path) | |
437 { | |
438 // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" | |
439 int cpl=pathSpec.length()-2; | |
440 if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl)) | |
441 { | |
442 if (path.length()==cpl || '/'==path.charAt(cpl)) | |
443 return true; | |
444 } | |
445 return false; | |
446 } | |
447 | |
448 | |
449 /* --------------------------------------------------------------- */ | |
450 /** Return the portion of a path that matches a path spec. | |
451 * @return null if no match at all. | |
452 */ | |
453 public static String pathMatch(String pathSpec, String path) | |
454 { | |
455 char c = pathSpec.charAt(0); | |
456 | |
457 if (c=='/') | |
458 { | |
459 if (pathSpec.length()==1) | |
460 return path; | |
461 | |
462 if (pathSpec.equals(path)) | |
463 return path; | |
464 | |
465 if (isPathWildcardMatch(pathSpec, path)) | |
466 return path.substring(0,pathSpec.length()-2); | |
467 } | |
468 else if (c=='*') | |
469 { | |
470 if (path.regionMatches(path.length()-(pathSpec.length()-1), | |
471 pathSpec,1,pathSpec.length()-1)) | |
472 return path; | |
473 } | |
474 return null; | |
475 } | |
476 | |
477 /* --------------------------------------------------------------- */ | |
478 /** Return the portion of a path that is after a path spec. | |
479 * @return The path info string | |
480 */ | |
481 public static String pathInfo(String pathSpec, String path) | |
482 { | |
483 if ("".equals(pathSpec)) | |
484 return path; //servlet 3 spec sec 12.2 will be '/' | |
485 | |
486 char c = pathSpec.charAt(0); | |
487 | |
488 if (c=='/') | |
489 { | |
490 if (pathSpec.length()==1) | |
491 return null; | |
492 | |
493 boolean wildcard = isPathWildcardMatch(pathSpec, path); | |
494 | |
495 // handle the case where pathSpec uses a wildcard and path info is "/*" | |
496 if (pathSpec.equals(path) && !wildcard) | |
497 return null; | |
498 | |
499 if (wildcard) | |
500 { | |
501 if (path.length()==pathSpec.length()-2) | |
502 return null; | |
503 return path.substring(pathSpec.length()-2); | |
504 } | |
505 } | |
506 return null; | |
507 } | |
508 | |
509 | |
510 /* ------------------------------------------------------------ */ | |
511 /** Relative path. | |
512 * @param base The base the path is relative to. | |
513 * @param pathSpec The spec of the path segment to ignore. | |
514 * @param path the additional path | |
515 * @return base plus path with pathspec removed | |
516 */ | |
517 public static String relativePath(String base, | |
518 String pathSpec, | |
519 String path ) | |
520 { | |
521 String info=pathInfo(pathSpec,path); | |
522 if (info==null) | |
523 info=path; | |
524 | |
525 if( info.startsWith( "./")) | |
526 info = info.substring( 2); | |
527 if( base.endsWith( URIUtil.SLASH)) | |
528 if( info.startsWith( URIUtil.SLASH)) | |
529 path = base + info.substring(1); | |
530 else | |
531 path = base + info; | |
532 else | |
533 if( info.startsWith( URIUtil.SLASH)) | |
534 path = base + info; | |
535 else | |
536 path = base + URIUtil.SLASH + info; | |
537 return path; | |
538 } | |
539 | |
540 /* ------------------------------------------------------------ */ | |
541 /* ------------------------------------------------------------ */ | |
542 /* ------------------------------------------------------------ */ | |
543 public static class Entry implements Map.Entry | |
544 { | |
545 private final Object key; | |
546 private final Object value; | |
547 private String mapped; | |
548 private transient String string; | |
549 | |
550 Entry(Object key, Object value) | |
551 { | |
552 this.key=key; | |
553 this.value=value; | |
554 } | |
555 | |
556 public Object getKey() | |
557 { | |
558 return key; | |
559 } | |
560 | |
561 public Object getValue() | |
562 { | |
563 return value; | |
564 } | |
565 | |
566 public Object setValue(Object o) | |
567 { | |
568 throw new UnsupportedOperationException(); | |
569 } | |
570 | |
571 @Override | |
572 public String toString() | |
573 { | |
574 if (string==null) | |
575 string=key+"="+value; | |
576 return string; | |
577 } | |
578 | |
579 public String getMapped() | |
580 { | |
581 return mapped; | |
582 } | |
583 | |
584 void setMapped(String mapped) | |
585 { | |
586 this.mapped = mapped; | |
587 } | |
588 } | |
589 } |