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 }