Mercurial Hosting > luan
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/PathMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,589 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http; + +import java.io.Externalizable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.URIUtil; + +/* ------------------------------------------------------------ */ +/** URI path map to Object. + * This mapping implements the path specification recommended + * in the 2.2 Servlet API. + * + * Path specifications can be of the following forms:<PRE> + * /foo/bar - an exact path specification. + * /foo/* - a prefix path specification (must end '/*'). + * *.ext - a suffix path specification. + * / - the default path specification. + * </PRE> + * Matching is performed in the following order <NL> + * <LI>Exact match. + * <LI>Longest prefix match. + * <LI>Longest suffix match. + * <LI>default. + * </NL> + * Multiple path specifications can be mapped by providing a list of + * specifications. By default this class uses characters ":," as path + * separators, unless configured differently by calling the static + * method @see PathMap#setPathSpecSeparators(String) + * <P> + * Special characters within paths such as '?� and ';' are not treated specially + * as it is assumed they would have been either encoded in the original URL or + * stripped from the path. + * <P> + * This class is not synchronized. If concurrent modifications are + * possible then it should be synchronized at a higher level. + * + * + */ +public class PathMap extends HashMap implements Externalizable +{ + /* ------------------------------------------------------------ */ + private static String __pathSpecSeparators = ":,"; + + /* ------------------------------------------------------------ */ + /** Set the path spec separator. + * Multiple path specification may be included in a single string + * if they are separated by the characters set in this string. + * By default this class uses ":," characters as path separators. + * @param s separators + */ + public static void setPathSpecSeparators(String s) + { + __pathSpecSeparators=s; + } + + /* --------------------------------------------------------------- */ + final StringMap _prefixMap=new StringMap(); + final StringMap _suffixMap=new StringMap(); + final StringMap _exactMap=new StringMap(); + + List _defaultSingletonList=null; + Entry _prefixDefault=null; + Entry _default=null; + final Set _entrySet; + boolean _nodefault=false; + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap() + { + super(11); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(boolean nodefault) + { + super(11); + _entrySet=entrySet(); + _nodefault=nodefault; + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(int capacity) + { + super (capacity); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct from dictionary PathMap. + */ + public PathMap(Map m) + { + putAll(m); + _entrySet=entrySet(); + } + + /* ------------------------------------------------------------ */ + public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException + { + HashMap map = new HashMap(this); + out.writeObject(map); + } + + /* ------------------------------------------------------------ */ + public void readExternal(java.io.ObjectInput in) + throws java.io.IOException, ClassNotFoundException + { + HashMap map = (HashMap)in.readObject(); + this.putAll(map); + } + + /* --------------------------------------------------------------- */ + /** Add a single path match to the PathMap. + * @param pathSpec The path specification, or comma separated list of + * path specifications. + * @param object The object the path maps to + */ + @Override + public Object put(Object pathSpec, Object object) + { + String str = pathSpec.toString(); + if ("".equals(str.trim())) + { + Entry entry = new Entry("",object); + entry.setMapped(""); + _exactMap.put("", entry); + return super.put("", object); + } + + StringTokenizer tok = new StringTokenizer(str,__pathSpecSeparators); + Object old =null; + + while (tok.hasMoreTokens()) + { + String spec=tok.nextToken(); + + if (!spec.startsWith("/") && !spec.startsWith("*.")) + throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); + + old = super.put(spec,object); + + // Make entry that was just created. + Entry entry = new Entry(spec,object); + + if (entry.getKey().equals(spec)) + { + if (spec.equals("/*")) + _prefixDefault=entry; + else if (spec.endsWith("/*")) + { + String mapped=spec.substring(0,spec.length()-2); + entry.setMapped(mapped); + _prefixMap.put(mapped,entry); + _exactMap.put(mapped,entry); + _exactMap.put(spec.substring(0,spec.length()-1),entry); + } + else if (spec.startsWith("*.")) + _suffixMap.put(spec.substring(2),entry); + else if (spec.equals(URIUtil.SLASH)) + { + if (_nodefault) + _exactMap.put(spec,entry); + else + { + _default=entry; + _defaultSingletonList= + Collections.singletonList(_default); + } + } + else + { + entry.setMapped(spec); + _exactMap.put(spec,entry); + } + } + } + + return old; + } + + /* ------------------------------------------------------------ */ + /** Get object matched by the path. + * @param path the path. + * @return Best matched object or null. + */ + public Object match(String path) + { + Map.Entry entry = getMatch(path); + if (entry!=null) + return entry.getValue(); + return null; + } + + + /* --------------------------------------------------------------- */ + /** Get the entry mapped by the best specification. + * @param path the path. + * @return Map.Entry of the best matched or null. + */ + public Entry getMatch(String path) + { + Map.Entry entry=null; + + if (path==null) + return null; + + int l=path.length(); + + //special case + if (l == 1 && path.charAt(0)=='/') + { + entry = (Map.Entry)_exactMap.get(""); + if (entry != null) + return (Entry)entry; + } + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + return (Entry) entry.getValue(); + + // prefix search + int i=l; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Prefix Default + if (_prefixDefault!=null) + return _prefixDefault; + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Default + return _default; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return LazyList of Map.Entry instances key=pathSpec + */ + public Object getLazyMatches(String path) + { + Map.Entry entry; + Object entries=null; + + if (path==null) + return LazyList.getList(entries); + + int l=path.length(); + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + + // prefix search + int i=l-1; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Prefix Default + if (_prefixDefault!=null) + entries=LazyList.add(entries,_prefixDefault); + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Default + if (_default!=null) + { + // Optimization for just the default + if (entries==null) + return _defaultSingletonList; + + entries=LazyList.add(entries,_default); + } + + return entries; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return List of Map.Entry instances key=pathSpec + */ + public List getMatches(String path) + { + return LazyList.getList(getLazyMatches(path)); + } + + /* --------------------------------------------------------------- */ + /** Return whether the path matches any entries in the PathMap, + * excluding the default entry + * @param path Path to match + * @return Whether the PathMap contains any entries that match this + */ + public boolean containsMatch(String path) + { + Entry match = getMatch(path); + return match!=null && !match.equals(_default); + } + + /* --------------------------------------------------------------- */ + @Override + public Object remove(Object pathSpec) + { + if (pathSpec!=null) + { + String spec=(String) pathSpec; + if (spec.equals("/*")) + _prefixDefault=null; + else if (spec.endsWith("/*")) + { + _prefixMap.remove(spec.substring(0,spec.length()-2)); + _exactMap.remove(spec.substring(0,spec.length()-1)); + _exactMap.remove(spec.substring(0,spec.length()-2)); + } + else if (spec.startsWith("*.")) + _suffixMap.remove(spec.substring(2)); + else if (spec.equals(URIUtil.SLASH)) + { + _default=null; + _defaultSingletonList=null; + } + else + _exactMap.remove(spec); + } + return super.remove(pathSpec); + } + + /* --------------------------------------------------------------- */ + @Override + public void clear() + { + _exactMap.clear(); + _prefixMap.clear(); + _suffixMap.clear(); + _default=null; + _defaultSingletonList=null; + super.clear(); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path) + throws IllegalArgumentException + { + return match(pathSpec, path, false); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path, boolean noDefault) + throws IllegalArgumentException + { + if (pathSpec.length()==0) + return "/".equals(path); + + char c = pathSpec.charAt(0); + if (c=='/') + { + if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) + return true; + + if(isPathWildcardMatch(pathSpec, path)) + return true; + } + else if (c=='*') + return path.regionMatches(path.length()-pathSpec.length()+1, + pathSpec,1,pathSpec.length()-1); + return false; + } + + /* --------------------------------------------------------------- */ + private static boolean isPathWildcardMatch(String pathSpec, String path) + { + // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" + int cpl=pathSpec.length()-2; + if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl)) + { + if (path.length()==cpl || '/'==path.charAt(cpl)) + return true; + } + return false; + } + + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that matches a path spec. + * @return null if no match at all. + */ + public static String pathMatch(String pathSpec, String path) + { + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return path; + + if (pathSpec.equals(path)) + return path; + + if (isPathWildcardMatch(pathSpec, path)) + return path.substring(0,pathSpec.length()-2); + } + else if (c=='*') + { + if (path.regionMatches(path.length()-(pathSpec.length()-1), + pathSpec,1,pathSpec.length()-1)) + return path; + } + return null; + } + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that is after a path spec. + * @return The path info string + */ + public static String pathInfo(String pathSpec, String path) + { + if ("".equals(pathSpec)) + return path; //servlet 3 spec sec 12.2 will be '/' + + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return null; + + boolean wildcard = isPathWildcardMatch(pathSpec, path); + + // handle the case where pathSpec uses a wildcard and path info is "/*" + if (pathSpec.equals(path) && !wildcard) + return null; + + if (wildcard) + { + if (path.length()==pathSpec.length()-2) + return null; + return path.substring(pathSpec.length()-2); + } + } + return null; + } + + + /* ------------------------------------------------------------ */ + /** Relative path. + * @param base The base the path is relative to. + * @param pathSpec The spec of the path segment to ignore. + * @param path the additional path + * @return base plus path with pathspec removed + */ + public static String relativePath(String base, + String pathSpec, + String path ) + { + String info=pathInfo(pathSpec,path); + if (info==null) + info=path; + + if( info.startsWith( "./")) + info = info.substring( 2); + if( base.endsWith( URIUtil.SLASH)) + if( info.startsWith( URIUtil.SLASH)) + path = base + info.substring(1); + else + path = base + info; + else + if( info.startsWith( URIUtil.SLASH)) + path = base + info; + else + path = base + URIUtil.SLASH + info; + return path; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class Entry implements Map.Entry + { + private final Object key; + private final Object value; + private String mapped; + private transient String string; + + Entry(Object key, Object value) + { + this.key=key; + this.value=value; + } + + public Object getKey() + { + return key; + } + + public Object getValue() + { + return value; + } + + public Object setValue(Object o) + { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() + { + if (string==null) + string=key+"="+value; + return string; + } + + public String getMapped() + { + return mapped; + } + + void setMapped(String mapped) + { + this.mapped = mapped; + } + } +}