net.lightbody.bmp.proxy.jetty.util.URI Maven / Gradle / Ivy
// ========================================================================
// $Id: URI.java,v 1.39 2006/01/04 13:55:31 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 net.lightbody.bmp.proxy.jetty.util;
import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import org.apache.commons.logging.Log;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/* ------------------------------------------------------------ */
/** 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.
* 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.39 2006/01/04 13:55:31 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("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;
/* ------------------------------------------------------------ */
/** 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)
{
if (buf==null)
{
loop:
for (int i=0;i=0)
{
buf=new StringBuffer(path.length()<<1);
break loop;
}
}
if (buf==null)
return null;
}
synchronized(buf)
{
for (int i=0;i=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=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 queryIdx=path.indexOf('?');
int start = path.lastIndexOf('/', (queryIdx > 0 ? queryIdx : 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=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='a'&&c<='z' ||
c>='A'&&c<='Z' ||
(i>0 &&(c>='0'&&c<='9' ||
c=='.' ||
c=='+' ||
c=='-'))
))
break;
}
return false;
}
}