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 } |
