diff --git a/apps/jetty/java/src/org/mortbay/util/FileResource.java b/apps/jetty/java/src/org/mortbay/util/FileResource.java new file mode 100644 index 0000000000000000000000000000000000000000..8788f14fdfb59228ee3c40c0fd351ebb90a2d9b6 --- /dev/null +++ b/apps/jetty/java/src/org/mortbay/util/FileResource.java @@ -0,0 +1,352 @@ +// ======================================================================== +// $Id: FileResource.java,v 1.31 2006/01/04 13:55:31 gregwilkins Exp $ +// Copyright 1996-2004 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ======================================================================== +package org.mortbay.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +import org.apache.commons.logging.Log; +import org.mortbay.log.LogFactory; + + +/* ------------------------------------------------------------ */ +/** File Resource. + * + * Handle resources of implied or explicit file type. + * This class can check for aliasing in the filesystem (eg case + * insensitivity). By default this is turned on if the platform does + * not have the "/" path separator, or it can be controlled with the + * "org.mortbay.util.FileResource.checkAliases" system parameter. + * + * If alias checking is turned on, then aliased resources are + * treated as if they do not exist, nor can they be created. + * + * @version $Revision: 1.31 $ + * @author Greg Wilkins (gregw) + */ +public class FileResource extends URLResource +{ + private static Log log = LogFactory.getLog(Credential.class); + private static boolean __checkAliases; + static + { + __checkAliases= + "true".equalsIgnoreCase + (System.getProperty("org.mortbay.util.FileResource.checkAliases","true")); + + if (__checkAliases) + log.info("Checking Resource aliases"); + } + + /* ------------------------------------------------------------ */ + private File _file; + private transient URL _alias=null; + private transient boolean _aliasChecked=false; + + /* ------------------------------------------------------------------------------- */ + /** setCheckAliases. + * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static void setCheckAliases(boolean checkAliases) + { + __checkAliases=checkAliases; + } + + /* ------------------------------------------------------------------------------- */ + /** getCheckAliases. + * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static boolean getCheckAliases() + { + return __checkAliases; + } + + /* -------------------------------------------------------- */ + FileResource(URL url) + throws IOException, URISyntaxException + { + super(url,null); + + try + { + // Try standard API to convert URL to file. + _file =new File(new URI(url.toString())); + } + catch (Exception e) + { + LogSupport.ignore(log,e); + try + { + // Assume that File.toURL produced unencoded chars. So try + // encoding them. + String urls= + "file:"+org.mortbay.util.URI.encodePath(url.toString().substring(5)); + _file =new File(new URI(urls)); + } + catch (Exception e2) + { + LogSupport.ignore(log,e2); + + // Still can't get the file. Doh! try good old hack! + checkConnection(); + Permission perm = _connection.getPermission(); + _file = new File(perm==null?url.getFile():perm.getName()); + } + } + + if (_file.isDirectory() && !_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + + /* -------------------------------------------------------- */ + FileResource(URL url, URLConnection connection, File file) + { + super(url,connection); + _file=file; + if (_file.isDirectory() && !_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + + /* -------------------------------------------------------- */ + public Resource addPath(String path) + throws IOException,MalformedURLException + { + FileResource r=null; + + if (!isDirectory()) + { + r=(FileResource)super.addPath(path); + } + else + { + path = org.mortbay.util.URI.canonicalPath(path); + + // treat all paths being added as relative + String rel=path; + if (path.startsWith("/")) + rel = path.substring(1); + + File newFile = new File(_file,rel.replace('/', File.separatorChar)); + r=new FileResource(newFile.toURI().toURL(),null,newFile); + } + + String encoded=org.mortbay.util.URI.encodePath(path); + int expected=r._urlString.length()-encoded.length(); + int index = r._urlString.lastIndexOf(encoded, expected); + + if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory())) + { + r._alias=r._url; + r._aliasChecked=true; + } + return r; + } + + + /* ------------------------------------------------------------ */ + public URL getAlias() + { + if (__checkAliases) { + if (!_aliasChecked) + { + try + { + String abs=_file.getAbsolutePath(); + String can=_file.getCanonicalPath(); + + if (abs.length()!=can.length() || !abs.equals(can)) + _alias=new File(can).toURI().toURL(); + + _aliasChecked=true; + + if (_alias!=null && log.isDebugEnabled()) + { + log.debug("ALIAS abs="+abs); + log.debug("ALIAS can="+can); + } + } + catch(Exception e) + { + log.warn(LogSupport.EXCEPTION,e); + return getURL(); + } + } + } else return null; + return _alias; + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the resource exists. + */ + public boolean exists() + { + return _file.exists(); + } + + /* -------------------------------------------------------- */ + /** + * Returns the last modified time + */ + public long lastModified() + { + return _file.lastModified(); + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the respresenetd resource is a container/directory. + */ + public boolean isDirectory() + { + return _file.isDirectory(); + } + + /* --------------------------------------------------------- */ + /** + * Return the length of the resource + */ + public long length() + { + return _file.length(); + } + + + /* --------------------------------------------------------- */ + /** + * Returns the name of the resource + */ + public String getName() + { + return _file.getAbsolutePath(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + public File getFile() + { + return _file; + } + + /* --------------------------------------------------------- */ + /** + * Returns an input stream to the resource + */ + public InputStream getInputStream() throws IOException + { + return new FileInputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Returns an output stream to the resource + */ + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + return new FileOutputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Deletes the given resource + */ + public boolean delete() + throws SecurityException + { + return _file.delete(); + } + + /* --------------------------------------------------------- */ + /** + * Rename the given resource + */ + public boolean renameTo( Resource dest) + throws SecurityException + { + if( dest instanceof FileResource) + return _file.renameTo( ((FileResource)dest)._file); + else + return false; + } + + /* --------------------------------------------------------- */ + /** + * Returns a list of resources contained in the given resource + */ + public String[] list() + { + String[] list =_file.list(); + if (list==null) + return null; + for (int i=list.length;i-->0;) + { + if (new File(_file,list[i]).isDirectory() && + !list[i].endsWith("/")) + list[i]+="/"; + } + return list; + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * File URIs are encoded. + * @param uri URI to encode. + * @return The uri unchanged. + */ + public String encode(String uri) + { + return uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param o + * @return + */ + public boolean equals( Object o) + { + if (this == o) + return true; + + if (null == o || ! (o instanceof FileResource)) + return false; + + FileResource f=(FileResource)o; + return f._file == _file || (null != _file && _file.equals(f._file)); + } + + /* ------------------------------------------------------------ */ + /** + * @return the hashcode. + */ + public int hashCode() + { + return null == _file ? super.hashCode() : _file.hashCode(); + } +} diff --git a/apps/jetty/java/src/org/mortbay/util/Resource.java b/apps/jetty/java/src/org/mortbay/util/Resource.java index 4180b8b011ccf33c3e45b5a1da9cf702cc08cc86..ed992cfd55f482ab7d0fa8a4cd3918fcc6c1cba0 100644 --- a/apps/jetty/java/src/org/mortbay/util/Resource.java +++ b/apps/jetty/java/src/org/mortbay/util/Resource.java @@ -392,7 +392,7 @@ public abstract class Resource implements Serializable buf.append(path); buf.append("\">"); buf.append(StringUtil.replace(StringUtil.replace(ls[i],"<","<"),">",">")); - buf.append(" "); + buf.append("</A> "); buf.append("</TD><TD ALIGN=right>"); buf.append(item.length()); buf.append(" bytes </TD><TD>"); diff --git a/apps/jetty/java/src/org/mortbay/util/URI.java b/apps/jetty/java/src/org/mortbay/util/URI.java new file mode 100644 index 0000000000000000000000000000000000000000..d5543832b77d67ff3e8c73a351e05b6f061e656f --- /dev/null +++ b/apps/jetty/java/src/org/mortbay/util/URI.java @@ -0,0 +1,995 @@ +// ======================================================================== +// $Id: URI.java,v 1.40 2009/05/16 02:02:00 gregwilkins Exp $ +// Copyright 199-2004 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ======================================================================== +package org.mortbay.util; + +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.mortbay.log.LogFactory; + +/* ------------------------------------------------------------ */ +/** URI Holder. + * This class assists with the decoding and encoding or HTTP URI's. + * It differs from the java.net.URL class as it does not provide + * communications ability, but it does assist with query string + * formatting. + * <P>ISO_8859_1 encoding is used by default for % encoded characters. This + * may be overridden with the org.mortbay.util.URI.charset system property. + * @see UrlEncoded + * @version $Id: URI.java,v 1.40 2009/05/16 02:02:00 gregwilkins Exp $ + * @author Greg Wilkins (gregw) + */ +public class URI + implements Cloneable +{ + private static Log log = LogFactory.getLog(URI.class); + + public static final String __CHARSET=System.getProperty("org.mortbay.util.URI.charset",StringUtil.__UTF_8); + public static final boolean __CHARSET_IS_DEFAULT=__CHARSET.equals(StringUtil.__UTF_8); + + /* ------------------------------------------------------------ */ + private String _uri; + private String _scheme; + private String _host; + private int _port; + private String _path; + private String _encodedPath; + private String _query; + private UrlEncoded _parameters; + private boolean _dirty; + private static String unreserved = "/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~"; + private static String reserved = "!*'();:@&=+$,?%#[]"; + private static String hexchars = "0123456789ABCDEF"; + + /* ------------------------------------------------------------ */ + /** Copy Constructor . + * @param uri + */ + public URI(URI uri) + throws IllegalArgumentException + { + _uri=uri.toString(); + _scheme=uri._scheme; + _host=uri._host; + _port=uri._port; + _path=uri._path; + _encodedPath=uri._encodedPath; + _query=uri._query; + if (uri._parameters!=null) + _parameters=(UrlEncoded)uri._parameters.clone(); + _dirty=false; + } + + /* ------------------------------------------------------------ */ + /** Construct from a String. + * The string must contain a URI path, but optionaly may contain a + * scheme, host, port and query string. + * + * @param uri [scheme://host[:port]]/path[?query] + */ + public URI(String uri) + throws IllegalArgumentException + { + setURI(uri); + } + + /* ------------------------------------------------------------ */ + public void setURI(String uri) + throws IllegalArgumentException + { + try + { + _uri=uri; + _scheme=null; + _host=null; + _port=0; + _path=null; + _encodedPath=null; + _query=null; + if (_parameters!=null) + _parameters.clear(); + + // Scan _uri for host, port, path & query + int maxi=uri.length()-1; + int mark=0; + int state=0; + int i=0; + + if (maxi==0 || uri.charAt(0)=='/' && uri.charAt(1)!='/') + { + state=3; + _scheme=null; + _host=null; + _port=0; + } + else + { + for (i=0;state<3 && i<=maxi;i++) + { + char c=uri.charAt(i); + switch(state) + { + case 0: // looking for scheme or path + if (c==':' && + uri.charAt(i+1)=='/' && + uri.charAt(i+2)=='/') + { + // found end of scheme & start of host + _scheme=uri.substring(mark,i); + i+=2; + mark=i+1; + state=1; + } + else if (i==0 && c=='/') + { + // Found path + state=3; + } + else if (i==0 && c=='*') + { + state=5; + _path="*"; + _encodedPath="*"; + } + continue; + + case 1: // Get host & look for port or path + if (c==':') + { + // found port + _host=uri.substring(mark,i); + mark=i+1; + state=2; + } + else if (c=='/') + { + // found path + _host=uri.substring(mark,i); + mark=i; + state=3; + } + continue; + + case 2: // Get port & look for path + if (c=='/') + { + _port=TypeUtil.parseInt(uri,mark,i-mark,10); + mark=i; + state=3; + } + continue; + } + } + } + + // State 3 - Get path & look for query + _query=null; + for (i++;i<=maxi;i++) + { + char c=uri.charAt(i); + if (c=='?') + { + // Found query + _encodedPath=uri.substring(mark,i); + _path=decodePath(_encodedPath); + + mark=i+1; + state=4; + break; + } + } + + // complete last state + switch(state) + { + case 0: + _dirty=false; + _encodedPath=_uri; + _path=decodePath(_encodedPath); + break; + + case 1: + _dirty=true; + _encodedPath="/"; + _path=_encodedPath; + _host=uri.substring(mark); + break; + + case 2: + _dirty=true; + _encodedPath="/"; + _path=_encodedPath; + _port=TypeUtil.parseInt(uri,mark,-1,10); + break; + case 3: + _dirty=(mark==maxi); + _encodedPath=uri.substring(mark); + _path=decodePath(_encodedPath); + break; + + case 4: + _dirty=false; + if (mark<=maxi) + _query=uri.substring(mark); + break; + + case 5: + _dirty=false; + } + + if (_query!=null && _query.length()>0) + { + if (_parameters==null) + _parameters= new UrlEncoded(); + else + _parameters.clear(); + _parameters.decode(_query,__CHARSET); + + } + else + _query=null; + } + catch (Exception e) + { + LogSupport.ignore(log,e); + throw new IllegalArgumentException("Malformed URI '"+uri+ + "' : "+e.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** Is the URI an absolute URL? + * @return True if the URI has a scheme or host + */ + public boolean isAbsolute() + { + return _scheme!=null || _host!=null; + } + + /* ------------------------------------------------------------ */ + /** Get the uri scheme. + * @return the URI scheme + */ + public String getScheme() + { + return _scheme; + } + + /* ------------------------------------------------------------ */ + /** Set the uri scheme. + * @param scheme the uri scheme + */ + public void setScheme(String scheme) + { + _scheme=scheme; + _dirty=true; + } + + /* ------------------------------------------------------------ */ + /** Get the uri host. + * @return the URI host + */ + public String getHost() + { + return _host; + } + + /* ------------------------------------------------------------ */ + /** Set the uri host. + * @param host the uri host + */ + public void setHost(String host) + { + _host=host; + _dirty=true; + } + + /* ------------------------------------------------------------ */ + /** Get the uri port. + * @return the URI port + */ + public int getPort() + { + return _port; + } + + /* ------------------------------------------------------------ */ + /** Set the uri port. + * A port of 0 implies use the default port. + * @param port the uri port + */ + public void setPort(int port) + { + _port=port; + _dirty=true; + } + + /* ------------------------------------------------------------ */ + /** Get the uri path. + * @return the URI path + */ + public String getPath() + { + return _path; + } + + /* ------------------------------------------------------------ */ + /** Get the encoded uri path. + * @return the URI path + */ + public String getEncodedPath() + { + return _encodedPath; + } + + /* ------------------------------------------------------------ */ + /** Set the uri path. + * @param path the URI path + */ + public void setPath(String path) + { + _path=path; + _encodedPath=encodePath(_path); + _dirty=true; + } + + + /* ------------------------------------------------------------ */ + /** Get the uri query String. + * @return the URI query string + */ + public String getQuery() + { + if (_dirty && _parameters!=null) + { + _query = _parameters.encode(__CHARSET); + if (_query!=null && _query.length()==0) + _query=null; + } + return _query; + } + + /* ------------------------------------------------------------ */ + /** Set the uri query String. + * @param query the URI query string + */ + public void setQuery(String query) + { + _query=query; + + if (_parameters!=null) + _parameters.clear(); + else if (query!=null) + _parameters=new UrlEncoded(); + + if (query!=null) + _parameters.decode(query,__CHARSET); + + cleanURI(); + } + + /* ------------------------------------------------------------ */ + /** Get the uri query _parameters names. + * @return Unmodifiable set of URI query _parameters names + */ + public Set getParameterNames() + { + if (_parameters==null) + return Collections.EMPTY_SET; + return _parameters.keySet(); + } + + /* ------------------------------------------------------------ */ + /** Get the uri query _parameters. + * @return the URI query _parameters + */ + public MultiMap getParameters() + { + if (_parameters==null) + _parameters=new UrlEncoded(); + _dirty=true; + return _parameters; + } + + /* ------------------------------------------------------------ */ + /** Get the uri query _parameters. + * @return the URI query _parameters in an unmodifiable map. + */ + public Map getUnmodifiableParameters() + { + if (_parameters==null) + return Collections.EMPTY_MAP; + return Collections.unmodifiableMap(_parameters); + } + + /* ------------------------------------------------------------ */ + /** Add the uri query _parameters to a MultiMap + */ + public void putParametersTo(MultiMap map) + { + if (_parameters!=null && _parameters.size()>0) + map.putAll(_parameters); + } + + /* ------------------------------------------------------------ */ + /** Clear the URI _parameters. + */ + public void clearParameters() + { + if (_parameters!=null) + { + _dirty=true; + _parameters.clear(); + } + } + + /* ------------------------------------------------------------ */ + /** Add encoded _parameters. + * @param encoded A HTTP encoded string of _parameters: e.g.. "a=1&b=2" + */ + public void put(String encoded) + { + UrlEncoded params = new UrlEncoded(encoded); + put(params); + } + + /* ------------------------------------------------------------ */ + /** Add name value pair to the uri query _parameters. + * @param name name of value + * @param value The value, which may be a multi valued list or + * String array. + */ + public Object put(Object name, Object value) + { + return getParameters().put(name,value); + } + + /* ------------------------------------------------------------ */ + /** Add dictionary to the uri query _parameters. + */ + public void put(Map values) + { + getParameters().putAll(values); + } + + /* ------------------------------------------------------------ */ + /** Get named value + */ + public String get(String name) + { + if (_parameters==null) + return null; + return (String)_parameters.get(name); + } + + /* ------------------------------------------------------------ */ + /** Get named multiple values. + * @param name The parameter name + * @return Umodifiable list of values or null + */ + public List getValues(String name) + { + if (_parameters==null) + return null; + return _parameters.getValues(name); + } + + /* ------------------------------------------------------------ */ + /** Remove named value + */ + public void remove(String name) + { + if (_parameters!=null) + { + _dirty= + _parameters.remove(name)!=null; + } + } + + /* ------------------------------------------------------------ */ + /** @return the URI string encoded. + */ + public String toString() + { + if (_dirty) + { + getQuery(); + cleanURI(); + } + return _uri; + } + + /* ------------------------------------------------------------ */ + private void cleanURI() + { + StringBuffer buf = new StringBuffer(_uri.length()*2); + synchronized(buf) + { + if (_scheme!=null) + { + buf.append(_scheme); + buf.append("://"); + buf.append(_host); + if (_port>0) + { + buf.append(':'); + buf.append(_port); + } + } + + buf.append(_encodedPath); + + if (_query!=null && _query.length()>0) + { + buf.append('?'); + buf.append(_query); + } + _uri=buf.toString(); + _dirty=false; + } + } + + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * This is the same encoding offered by URLEncoder, except that + * the '/' character is not encoded. + * @param path The path the encode + * @return The encoded path + */ + public static String encodePath(String path) + { + if (path==null || path.length()==0) + return path; + + StringBuffer buf = encodePath(null,path); + return buf==null?path:buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuffer to encode path into (or null) + * @return The StringBuffer or null if no substitutions required. + */ + public static StringBuffer encodePath(StringBuffer buf, String path) + { + // Convert path to native first. + byte[] b = null; + /* + try { + b = path.getBytes(__CHARSET); + } catch(UnsupportedEncodingException ex) { + return null; // Shouldn't be possible. + } + */ + b = path.getBytes(); + StringBuffer x = new StringBuffer(b.length); + for(int i=0; i<b.length; i++) { + x.append((char)(b[i]&0xff)); + } + String _path = new String(x); + if(buf == null) { + loop: + for(int i = 0; i < _path.length(); i++) { + char c = _path.charAt(i); + String cs = "" + c; + if(reserved.contains(cs) || !unreserved.contains(cs)) { + buf = new StringBuffer(_path.length() << 1); + break loop; + } + } + if(buf == null) { + return null; + } + } + synchronized(buf) { + for(int i = 0; i < _path.length(); i++) { + char c = _path.charAt(i); + String cs = "" + c; + if(reserved.contains(cs) || !unreserved.contains(cs)) { + if((c & 0xff) == c) { + buf.append(gethex(c & 0xff)); + } else { + buf.append(gethex((c >> 8) & 0xff)); + buf.append(gethex(c & 0xff)); + } + } else { + buf.append(c); + } + } + } + + return buf; + } + + /** + * + * @param decimal value not greater than 255. + * @return a percent sign followed by two hexadecimal digits. + */ + private static String gethex(int decimal) { + return new String("%" + hexchars.charAt(decimal >> 4) + hexchars.charAt(decimal & 0xF)); + } + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuffer to encode path into (or null) + * @param encode String of characters to encode. % is always encoded. + * @return The StringBuffer or null if no substitutions required. + */ + public static StringBuffer encodeString(StringBuffer buf, + String path, + String encode) + { + if (buf==null) + { + loop: + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + if (c=='%' || encode.indexOf(c)>=0) + { + buf=new StringBuffer(path.length()<<1); + break loop; + } + } + if (buf==null) + return null; + } + + synchronized(buf) + { + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + if (c=='%' || encode.indexOf(c)>=0) + { + buf.append('%'); + StringUtil.append(buf,(byte)(0xff&c),16); + } + else + buf.append(c); + } + } + + return buf; + } + + /* ------------------------------------------------------------ */ + /* Decode a URI path. + * @param path The path the encode + * @param buf StringBuffer to encode path into + */ + public static String decodePath(String path) + { + int len=path.length(); + byte[] bytes=null; + int n=0; + boolean noDecode=true; + + for (int i=0;i<len;i++) + { + char c = path.charAt(i); + + byte b = (byte)(0xff & c); + + if (c=='%' && (i+2)<len) + { + noDecode=false; + b=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16)); + i+=2; + } + else if (bytes==null) + { + n++; + continue; + } + + if (bytes==null) + { + noDecode=false; + bytes=new byte[len]; + for (int j=0;j<n;j++) + bytes[j]=(byte)(0xff & path.charAt(j)); + } + + bytes[n++]=b; + } + + if (noDecode) + return path; + + /* + try + { + return new String(bytes,0,n,__CHARSET); + } + catch(UnsupportedEncodingException e) + { + log.warn(LogSupport.EXCEPTION,e); + return new String(bytes,0,n); + } + */ + + return new String(bytes,0,n); + + } + + /* ------------------------------------------------------------ */ + /** Clone URI. + * @return cloned URI + */ + public Object clone() + throws CloneNotSupportedException + { + URI u = (URI)super.clone(); + if (_parameters!=null) + u._parameters=(UrlEncoded)_parameters.clone(); + _dirty=false; + + return u; + } + + + /* ------------------------------------------------------------ */ + /** Add two URI path segments. + * Handles null and empty paths, path and query params (eg ?a=b or + * ;JSESSIONID=xxx) and avoids duplicate '/' + * @param p1 URI path segment + * @param p2 URI path segment + * @return Legally combined path segments. + */ + public static String addPaths(String p1, String p2) + { + if (p1==null || p1.length()==0) + { + if (p2==null || p2.length()==0) + return p1; + return p2; + } + if (p2==null || p2.length()==0) + return p1; + + int split=p1.indexOf(';'); + if (split<0) + split=p1.indexOf('?'); + if (split==0) + return p2+p1; + if (split<0) + split=p1.length(); + + StringBuffer buf = new StringBuffer(p1.length()+p2.length()+2); + buf.append(p1); + + if (buf.charAt(split-1)=='/') + { + if (p2.startsWith("/")) + { + buf.deleteCharAt(split-1); + buf.insert(split-1,p2); + } + else + buf.insert(split,p2); + } + else + { + if (p2.startsWith("/")) + buf.insert(split,p2); + else + { + buf.insert(split,'/'); + buf.insert(split+1,p2); + } + } + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Return the parent Path. + * Treat a URI like a directory path and return the parent directory. + */ + public static String parentPath(String p) + { + if (p==null || "/".equals(p)) + return null; + int slash=p.lastIndexOf('/',p.length()-2); + if (slash>=0) + return p.substring(0,slash+1); + return null; + } + + /* ------------------------------------------------------------ */ + /** Strip parameters from a path. + * Return path upto any semicolon parameters. + */ + public static String stripPath(String path) + { + if (path==null) + return null; + int semi=path.indexOf(';'); + if (semi<0) + return path; + return path.substring(0,semi); + } + + /* ------------------------------------------------------------ */ + /** Convert a path to a cananonical form. + * All instances of "." and ".." are factored out. Null is returned + * if the path tries to .. above it's root. + * @param path + * @return path or null. + */ + public static String canonicalPath(String path) + { + if (path==null || path.length()==0) + return path; + + int end=path.length(); + int start = path.lastIndexOf('/', end); + + search: + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (path.charAt(start+1)!='.') + break; + break search; + case 3: // possible double dot + if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.') + break; + break search; + } + + end=start; + start=path.lastIndexOf('/',end-1); + } + + // If we have checked the entire string + if (start>=end) + return path; + + StringBuffer buf = new StringBuffer(path); + int delStart=-1; + int delEnd=-1; + int skip=0; + + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (buf.charAt(start+1)!='.') + { + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/') + break; + + if(delEnd<0) + delEnd=end; + delStart=start; + if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/') + { + delStart++; + if (delEnd<buf.length() && buf.charAt(delEnd)=='/') + delEnd++; + break; + } + if (end==buf.length()) + delStart++; + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + case 3: // possible double dot + if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.') + { + if (skip>0 && --skip==0) + { delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + delStart=start; + if (delEnd<0) + delEnd=end; + + skip++; + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + default: + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + } + + // Do the delete + if (skip<=0 && delStart>=0 && delStart>=0) + { + buf.delete(delStart,delEnd); + delStart=delEnd=-1; + if (skip>0) + delEnd=end; + } + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + } + + // Too many .. + if (skip>0) + return null; + + // Do the delete + if (delEnd>=0) + buf.delete(delStart,delEnd); + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param uri URI + * @return True if the uri has a scheme + */ + public static boolean hasScheme(String uri) + { + for (int i=0;i<uri.length();i++) + { + char c=uri.charAt(i); + if (c==':') + return true; + if (!(c>='a'&&c<='z' || + c>='A'&&c<='Z' || + (i>0 &&(c>='0'&&c<='9' || + c=='.' || + c=='+' || + c=='-')) + )) + break; + } + return false; + } + +} + + +