/*
* @(#)Util.java 0.3-3 06/05/2001
*
* This file is part of the HTTPClient package
* Copyright (C) 1996-2001 Ronald Tschal�r
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307, USA
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* ronald@innovation.ch
*
* The HTTPClient's home page is located at:
*
* http://www.innovation.ch/java/HTTPClient/
*
*/
package HTTPClient;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.Date;
import java.util.BitSet;
import java.util.Locale;
import java.util.Vector;
import java.util.Hashtable;
import java.util.SimpleTimeZone;
import java.util.StringTokenizer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* This class holds various utility methods.
*
* @version 0.3-3 06/05/2001
* @author Ronald Tschal�r
*/
public class Util
{
private static final BitSet Separators = new BitSet(128);
private static final BitSet TokenChar = new BitSet(128);
private static final BitSet UnsafeChar = new BitSet(128);
private static DateFormat http_format;
private static DateFormat parse_1123;
private static DateFormat parse_850;
private static DateFormat parse_asctime;
private static final Object http_format_lock = new Object();
private static final Object http_parse_lock = new Object();
static
{
// rfc-2616 tspecial
Separators.set('(');
Separators.set(')');
Separators.set('<');
Separators.set('>');
Separators.set('@');
Separators.set(',');
Separators.set(';');
Separators.set(':');
Separators.set('\\');
Separators.set('"');
Separators.set('/');
Separators.set('[');
Separators.set(']');
Separators.set('?');
Separators.set('=');
Separators.set('{');
Separators.set('}');
Separators.set(' ');
Separators.set('\t');
// rfc-2616 token
for (int ch=32; ch<127; ch++) TokenChar.set(ch);
TokenChar.xor(Separators);
// rfc-1738 unsafe characters, including CTL and SP, and excluding
// "#" and "%"
for (int ch=0; ch<32; ch++) UnsafeChar.set(ch);
UnsafeChar.set(' ');
UnsafeChar.set('<');
UnsafeChar.set('>');
UnsafeChar.set('"');
UnsafeChar.set('{');
UnsafeChar.set('}');
UnsafeChar.set('|');
UnsafeChar.set('\\');
UnsafeChar.set('^');
UnsafeChar.set('~');
UnsafeChar.set('[');
UnsafeChar.set(']');
UnsafeChar.set('`');
UnsafeChar.set(127);
// rfc-1123 date format (restricted to GMT, as per rfc-2616)
/* This initialization has been moved to httpDate() because it
* takes an awfully long time and is often not needed
*
http_format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
Locale.US);
http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
*/
}
// Constructors
/**
* This class isn't meant to be instantiated.
*/
private Util() {}
// Methods
final static Object[] resizeArray(Object[] src, int new_size)
{
Class compClass = src.getClass().getComponentType();
Object tmp[] = (Object[]) Array.newInstance(compClass, new_size);
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static NVPair[] resizeArray(NVPair[] src, int new_size)
{
NVPair tmp[] = new NVPair[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static AuthorizationInfo[] resizeArray(AuthorizationInfo[] src,
int new_size)
{
AuthorizationInfo tmp[] = new AuthorizationInfo[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static Cookie[] resizeArray(Cookie[] src, int new_size)
{
Cookie tmp[] = new Cookie[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static String[] resizeArray(String[] src, int new_size)
{
String tmp[] = new String[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static boolean[] resizeArray(boolean[] src, int new_size)
{
boolean tmp[] = new boolean[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static byte[] resizeArray(byte[] src, int new_size)
{
byte tmp[] = new byte[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static char[] resizeArray(char[] src, int new_size)
{
char tmp[] = new char[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
final static int[] resizeArray(int[] src, int new_size)
{
int tmp[] = new int[new_size];
System.arraycopy(src, 0, tmp, 0,
(src.length < new_size ? src.length : new_size));
return tmp;
}
/**
* Split a property into an array of Strings, using "|" as the
* separator.
*/
static String[] splitProperty(String prop)
{
if (prop == null) return new String[0];
StringTokenizer tok = new StringTokenizer(prop, "|");
String[] list = new String[tok.countTokens()];
for (int idx=0; idx<list.length; idx++)
list[idx] = tok.nextToken().trim();
return list;
}
/**
* Helper method for context lists used by modules. Returns the
* list associated with the context if it exists; otherwise it creates
* a new list and adds it to the context list.
*
* @param cntxt_list the list of lists indexed by context
* @param cntxt the context
*/
final static Hashtable getList(Hashtable cntxt_list, Object cntxt)
{
synchronized (cntxt_list)
{
Hashtable list = (Hashtable) cntxt_list.get(cntxt);
if (list == null)
{
list = new Hashtable();
cntxt_list.put(cntxt, list);
}
return list;
}
}
/**
* Creates an array of distances to speed up the search in findStr().
* The returned array should be passed as the second argument to
* findStr().
*
* @param search the search string (same as the first argument to
* findStr()).
* @return an array of distances (to be passed as the second argument to
* findStr()).
*/
final static int[] compile_search(byte[] search)
{
int[] cmp = {0, 1, 0, 1, 0, 1};
int end;
for (int idx=0; idx<search.length; idx++)
{
for (end=idx+1; end<search.length; end++)
{
if (search[idx] == search[end]) break;
}
if (end < search.length)
{
if ((end-idx) > cmp[1])
{
cmp[4] = cmp[2];
cmp[5] = cmp[3];
cmp[2] = cmp[0];
cmp[3] = cmp[1];
cmp[0] = idx;
cmp[1] = end - idx;
}
else if ((end-idx) > cmp[3])
{
cmp[4] = cmp[2];
cmp[5] = cmp[3];
cmp[2] = idx;
cmp[3] = end - idx;
}
else if ((end-idx) > cmp[3])
{
cmp[4] = idx;
cmp[5] = end - idx;
}
}
}
cmp[1] += cmp[0];
cmp[3] += cmp[2];
cmp[5] += cmp[4];
return cmp;
}
/**
* Search for a string. Use compile_search() to first generate the second
* argument. This uses a Knuth-Morris-Pratt like algorithm.
*
* @param search the string to search for.
* @param cmp the the array returned by compile_search.
* @param str the string in which to look for <var>search</var>.
* @param beg the position at which to start the search in
* <var>str</var>.
* @param end the position at which to end the search in <var>str</var>,
* noninclusive.
* @return the position in <var>str</var> where <var>search</var> was
* found, or -1 if not found.
*/
final static int findStr(byte[] search, int[] cmp, byte[] str,
int beg, int end)
{
int c1f = cmp[0],
c1l = cmp[1],
d1 = c1l - c1f,
c2f = cmp[2],
c2l = cmp[3],
d2 = c2l - c2f,
c3f = cmp[4],
c3l = cmp[5],
d3 = c3l - c3f;
Find: while (beg+search.length <= end)
{
if (search[c1l] == str[beg+c1l])
{
/* This is correct, but Visual J++ can't cope with it...
Comp: if (search[c1f] == str[beg+c1f])
{
for (int idx=0; idx<search.length; idx++)
if (search[idx] != str[beg+idx]) break Comp;
break Find; // we found it
}
* so here is the replacement: */
if (search[c1f] == str[beg+c1f])
{
boolean same = true;
for (int idx=0; idx<search.length; idx++)
if (search[idx] != str[beg+idx])
{
same = false;
break;
}
if (same)
break Find; // we found it
}
beg += d1;
}
else if (search[c2l] == str[beg+c2l])
beg += d2;
else if (search[c3l] == str[beg+c3l])
beg += d3;
else
beg++;
}
if (beg+search.length > end)
return -1;
else
return beg;
}
/**
* Replace quoted characters by their unquoted version. Quoted characters
* are characters preceded by a slash. E.g. "\c" would be replaced by "c".
* This is used in parsing http headers where quoted-characters are
* allowed in quoted-strings and often used to quote the quote character
* <">.
*
* @param str the string do dequote
* @return the string do with all quoted characters replaced by their
* true value.
*/
public final static String dequoteString(String str)
{
if (str.indexOf('\\') == -1) return str;
char[] buf = str.toCharArray();
int pos = 0, num_deq = 0;
while (pos < buf.length)
{
if (buf[pos] == '\\' && pos+1 < buf.length)
{
System.arraycopy(buf, pos+1, buf, pos, buf.length-pos-1);
num_deq++;
}
pos++;
}
return new String(buf, 0, buf.length-num_deq);
}
/**
* Replace given characters by their quoted version. Quoted characters
* are characters preceded by a slash. E.g. "c" would be replaced by "\c".
* This is used in generating http headers where certain characters need
* to be quoted, such as the quote character <">.
*
* @param str the string do quote
* @param qlist the list of characters to quote
* @return the string do with all characters replaced by their
* quoted version.
*/
public final static String quoteString(String str, String qlist)
{
char[] list = qlist.toCharArray();
int idx;
for (idx=0; idx<list.length; idx++)
if (str.indexOf(list[idx]) != -1) break;
if (idx == list.length) return str;
int len = str.length();
char[] buf = new char[len*2];
str.getChars(0, len, buf, 0);
int pos = 0;
while (pos < len)
{
if (qlist.indexOf(buf[pos], 0) != -1)
{
if (len == buf.length)
buf = Util.resizeArray(buf, len+str.length());
System.arraycopy(buf, pos, buf, pos+1, len-pos);
len++;
buf[pos++] = '\\';
}
pos++;
}
return new String(buf, 0, len);
}
/**
* This parses the value part of a header. All quoted strings are
* dequoted.
*
* @see #parseHeader(java.lang.String, boolean)
* @param header the value part of the header.
* @return a Vector containing all the elements; each entry is an
* instance of <var>HttpHeaderElement</var>.
* @exception ParseException if the syntax rules are violated.
*/
public final static Vector parseHeader(String header) throws ParseException
{
return parseHeader(header, true);
}
/**
* This parses the value part of a header. The result is a Vector of
* HttpHeaderElement's. The syntax the header must conform to is:
*
* <PRE>
* header = [ element ] *( "," [ element ] )
* element = name [ "=" [ value ] ] *( ";" [ param ] )
* param = name [ "=" [ value ] ]
*
* name = token
* value = ( token | quoted-string )
*
* token = 1*<any char except "=", ",", ";", <"> and
* white space>
* quoted-string = <"> *( text | quoted-char ) <">
* text = any char except <">
* quoted-char = "\" char
* </PRE>
*
* Any amount of white space is allowed between any part of the header,
* element or param and is ignored. A missing value in any element or
* param will be stored as the empty string; if the "=" is also missing
* <var>null</var> will be stored instead.
*
* @param header the value part of the header.
* @param dequote if true all quoted strings are dequoted.
* @return a Vector containing all the elements; each entry is an
* instance of <var>HttpHeaderElement</var>.
* @exception ParseException if the above syntax rules are violated.
* @see HTTPClient.HttpHeaderElement
*/
public final static Vector parseHeader(String header, boolean dequote)
throws ParseException
{
if (header == null) return null;
char[] buf = header.toCharArray();
Vector elems = new Vector();
boolean first = true;
int beg = -1, end = 0, len = buf.length, abeg[] = new int[1];
String elem_name, elem_value;
elements: while (true)
{
if (!first) // find required ","
{
beg = skipSpace(buf, end);
if (beg == len) break;
if (buf[beg] != ',')
throw new ParseException("Bad header format: '" + header +
"'\nExpected \",\" at position " +
beg);
}
first = false;
beg = skipSpace(buf, beg+1);
if (beg == len) break elements;
if (buf[beg] == ',') // skip empty elements
{
end = beg;
continue elements;
}
if (buf[beg] == '=' || buf[beg] == ';' || buf[beg] == '"')
throw new ParseException("Bad header format: '" + header +
"'\nEmpty element name at position " +
beg);
end = beg+1; // extract element name
while (end < len && !Character.isWhitespace(buf[end]) &&
buf[end] != '=' && buf[end] != ',' && buf[end] != ';')
end++;
elem_name = new String(buf, beg, end-beg);
beg = skipSpace(buf, end);
if (beg < len && buf[beg] == '=') // element value
{
abeg[0] = beg+1;
elem_value = parseValue(buf, abeg, header, dequote);
end = abeg[0];
}
else
{
elem_value = null;
end = beg;
}
NVPair[] params = new NVPair[0];
params: while (true)
{
String param_name, param_value;
beg = skipSpace(buf, end); // expect ";"
if (beg == len || buf[beg] != ';')
break params;
beg = skipSpace(buf, beg+1);
if (beg == len || buf[beg] == ',')
{
end = beg;
break params;
}
if (buf[beg] == ';') // skip empty parameters
{
end = beg;
continue params;
}
if (buf[beg] == '=' || buf[beg] == '"')
throw new ParseException("Bad header format: '" + header +
"'\nEmpty parameter name at position "+
beg);
end = beg+1; // extract param name
while (end < len && !Character.isWhitespace(buf[end]) &&
buf[end] != '=' && buf[end] != ',' && buf[end] != ';')
end++;
param_name = new String(buf, beg, end-beg);
beg = skipSpace(buf, end);
if (beg < len && buf[beg] == '=') // element value
{
abeg[0] = beg+1;
param_value = parseValue(buf, abeg, header, dequote);
end = abeg[0];
}
else
{
param_value = null;
end = beg;
}
params = Util.resizeArray(params, params.length+1);
params[params.length-1] = new NVPair(param_name, param_value);
}
elems.addElement(
new HttpHeaderElement(elem_name, elem_value, params));
}
return elems;
}
/**
* Parse the value part. Accepts either token or quoted string.
*/
private static String parseValue(char[] buf, int[] abeg, String header,
boolean dequote)
throws ParseException
{
int beg = abeg[0], end = beg, len = buf.length;
String value;
beg = skipSpace(buf, beg);
if (beg < len && buf[beg] == '"') // it's a quoted-string
{
beg++;
end = beg;
char[] deq_buf = null;
int deq_pos = 0, lst_pos = beg;
while (end < len && buf[end] != '"')
{
if (buf[end] == '\\')
{
if (dequote) // dequote char
{
if (deq_buf == null)
deq_buf = new char[buf.length];
System.arraycopy(buf, lst_pos, deq_buf, deq_pos,
end-lst_pos);
deq_pos += end-lst_pos;
lst_pos = ++end;
}
else
end++; // skip quoted char
}
end++;
}
if (end == len)
throw new ParseException("Bad header format: '" + header +
"'\nClosing <\"> for quoted-string"+
" starting at position " +
(beg-1) + " not found");
if (deq_buf != null)
{
System.arraycopy(buf, lst_pos, deq_buf, deq_pos, end-lst_pos);
deq_pos += end-lst_pos;
value = new String(deq_buf, 0, deq_pos);
}
else
value = new String(buf, beg, end-beg);
end++;
}
else // it's a simple token value
{
end = beg;
while (end < len && !Character.isWhitespace(buf[end]) &&
buf[end] != ',' && buf[end] != ';')
end++;
value = new String(buf, beg, end-beg);
}
abeg[0] = end;
return value;
}
/**
* Determines if the given header contains a certain token. The header
* must conform to the rules outlined in parseHeader().
*
* @see #parseHeader(java.lang.String)
* @param header the header value.
* @param token the token to find; the match is case-insensitive.
* @return true if the token is present, false otherwise.
* @exception ParseException if this is thrown parseHeader().
*/
public final static boolean hasToken(String header, String token)
throws ParseException
{
if (header == null)
return false;
else
return parseHeader(header).contains(new HttpHeaderElement(token));
}
/**
* Get the HttpHeaderElement with the name <var>name</var>.
*
* @param header a vector of HttpHeaderElement's, such as is returned
* from <code>parseHeader()</code>
* @param name the name of element to retrieve; matching is
* case-insensitive
* @return the request element, or null if none found.
* @see #parseHeader(java.lang.String)
*/
public final static HttpHeaderElement getElement(Vector header, String name)
{
int idx = header.indexOf(new HttpHeaderElement(name));
if (idx == -1)
return null;
else
return (HttpHeaderElement) header.elementAt(idx);
}
/**
* retrieves the value associated with the parameter <var>param</var> in
* a given header string. It parses the header using
* <code>parseHeader()</code> and then searches the first element for the
* given parameter. This is used especially in headers like
* 'Content-type' and 'Content-Disposition'.
*
* <P>quoted characters ("\x") in a quoted string are dequoted.
*
* @see #parseHeader(java.lang.String)
* @param param the parameter name
* @param hdr the header value
* @return the value for this parameter, or null if not found.
* @exception ParseException if the above syntax rules are violated.
*/
public final static String getParameter(String param, String hdr)
throws ParseException
{
NVPair[] params = ((HttpHeaderElement) parseHeader(hdr).firstElement()).
getParams();
for (int idx=0; idx<params.length; idx++)
{
if (params[idx].getName().equalsIgnoreCase(param))
return params[idx].getValue();
}
return null;
}
/**
* Assembles a Vector of HttpHeaderElements into a full header string.
* The individual header elements are seperated by a ", ".
*
* @param the parsed header
* @return a string containing the assembled header
*/
public final static String assembleHeader(Vector pheader)
{
StringBuffer hdr = new StringBuffer(200);
int len = pheader.size();
for (int idx=0; idx<len; idx++)
{
((HttpHeaderElement) pheader.elementAt(idx)).appendTo(hdr);
hdr.append(", ");
}
hdr.setLength(hdr.length()-2);
return hdr.toString();
}
/**
* returns the position of the first non-space character in a char array
* starting a position pos.
*
* @param str the char array
* @param pos the position to start looking
* @return the position of the first non-space character
*/
final static int skipSpace(char[] str, int pos)
{
int len = str.length;
while (pos < len && Character.isWhitespace(str[pos])) pos++;
return pos;
}
/**
* returns the position of the first space character in a char array
* starting a position pos.
*
* @param str the char array
* @param pos the position to start looking
* @return the position of the first space character, or the length of
* the string if not found
*/
final static int findSpace(char[] str, int pos)
{
int len = str.length;
while (pos < len && !Character.isWhitespace(str[pos])) pos++;
return pos;
}
/**
* returns the position of the first non-token character in a char array
* starting a position pos.
*
* @param str the char array
* @param pos the position to start looking
* @return the position of the first non-token character, or the length
* of the string if not found
*/
final static int skipToken(char[] str, int pos)
{
int len = str.length;
while (pos < len && TokenChar.get(str[pos])) pos++;
return pos;
}
/**
* Does the string need to be quoted when sent in a header? I.e. does
* it contain non-token characters?
*
* @param str the string
* @return true if it needs quoting (i.e. it contains non-token chars)
*/
final static boolean needsQuoting(String str)
{
int len = str.length(), pos = 0;
while (pos < len && TokenChar.get(str.charAt(pos))) pos++;
return (pos < len);
}
/**
* Compares two http urls for equality. This exists because the method
* <code>java.net.URL.sameFile()</code> is broken (an explicit port 80
* doesn't compare equal to an implicit port, and it doesn't take
* escapes into account).
*
* <P>Two http urls are considered equal if they have the same protocol
* (case-insensitive match), the same host (case-insensitive), the
* same port and the same file (after decoding escaped characters).
*
* @param url1 the first url
* @param url1 the second url
* @return true if <var>url1</var> and <var>url2</var> compare equal
*/
public final static boolean sameHttpURL(URL url1, URL url2)
{
if (!url1.getProtocol().equalsIgnoreCase(url2.getProtocol()))
return false;
if (!url1.getHost().equalsIgnoreCase(url2.getHost()))
return false;
int port1 = url1.getPort(), port2 = url2.getPort();
if (port1 == -1) port1 = URI.defaultPort(url1.getProtocol());
if (port2 == -1) port2 = URI.defaultPort(url1.getProtocol());
if (port1 != port2)
return false;
try
{ return URI.unescape(url1.getFile(), null).equals(URI.unescape(url2.getFile(), null)); }
catch (ParseException pe)
{ return url1.getFile().equals(url2.getFile());}
}
/**
* Return the default port used by a given protocol.
*
* @param protocol the protocol
* @return the port number, or 0 if unknown
* @deprecated use URI.defaultPort() instead
* @see HTTPClient.URI#defaultPort(java.lang.String)
*/
public final static int defaultPort(String protocol)
{
return URI.defaultPort(protocol);
}
/**
* Parse the http date string. java.util.Date will do this fine, but
* is deprecated, so we use SimpleDateFormat instead.
*
* @param dstr the date string to parse
* @return the Date object
*/
final static Date parseHttpDate(String dstr)
{
synchronized (http_parse_lock)
{
if (parse_1123 == null)
setupParsers();
}
try
{ return parse_1123.parse(dstr); }
catch (java.text.ParseException pe)
{ }
try
{ return parse_850.parse(dstr); }
catch (java.text.ParseException pe)
{ }
try
{ return parse_asctime.parse(dstr); }
catch (java.text.ParseException pe)
{ throw new IllegalArgumentException(pe.toString()); }
}
private static final void setupParsers()
{
parse_1123 =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
parse_850 =
new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss 'GMT'", Locale.US);
parse_asctime =
new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
parse_1123.setTimeZone(new SimpleTimeZone(0, "GMT"));
parse_850.setTimeZone(new SimpleTimeZone(0, "GMT"));
parse_asctime.setTimeZone(new SimpleTimeZone(0, "GMT"));
parse_1123.setLenient(true);
parse_850.setLenient(true);
parse_asctime.setLenient(true);
}
/**
* This returns a string containing the date and time in <var>date</var>
* formatted according to a subset of RFC-1123. The format is defined in
* the HTTP/1.0 spec (RFC-1945), section 3.3, and the HTTP/1.1 spec
* (RFC-2616), section 3.3.1. Note that Date.toGMTString() is close, but
* is missing the weekday and supresses the leading zero if the day is
* less than the 10th. Instead we use the SimpleDateFormat class.
*
* <P>Some versions of JDK 1.1.x are bugged in that their GMT uses
* daylight savings time... Therefore we use our own timezone
* definitions.
*
* @param date the date and time to be converted
* @return a string containg the date and time as used in http
*/
public static final String httpDate(Date date)
{
synchronized (http_format_lock)
{
if (http_format == null)
setupFormatter();
}
return http_format.format(date);
}
private static final void setupFormatter()
{
http_format =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
}
/**
* Escape unsafe characters in a path.
*
* @param path the original path
* @return the path with all unsafe characters escaped
*/
final static String escapeUnsafeChars(String path)
{
int len = path.length();
char[] buf = new char[3*len];
int dst = 0;
for (int src=0; src<len; src++)
{
char ch = path.charAt(src);
if (ch >= 128 || UnsafeChar.get(ch))
{
buf[dst++] = '%';
buf[dst++] = hex_map[(ch & 0xf0) >>> 4];
buf[dst++] = hex_map[ch & 0x0f];
}
else
buf[dst++] = ch;
}
if (dst > len)
return new String(buf, 0, dst);
else
return path;
}
static final char[] hex_map =
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
/**
* Extract the path from an http resource.
*
* <P>The "resource" part of an HTTP URI can contain a number of parts,
* some of which are not always of interest. These methods here will
* extract the various parts, assuming the following syntanx (taken from
* RFC-2616):
*
* <PRE>
* resource = [ "/" ] [ path ] [ ";" params ] [ "?" query ] [ "#" fragment ]
* </PRE>
*
* @param the resource to split
* @return the path, including any leading "/"
* @see #getParams
* @see #getQuery
* @see #getFragment
*/
public final static String getPath(String resource)
{
int p, end = resource.length();
if ((p = resource.indexOf('#')) != -1) // find fragment
end = p;
if ((p = resource.indexOf('?')) != -1 && p < end) // find query
end = p;
if ((p = resource.indexOf(';')) != -1 && p < end) // find params
end = p;
return resource.substring(0, end);
}
/**
* Extract the params part from an http resource.
*
* @param the resource to split
* @return the params, or null if there are none
* @see #getPath
*/
public final static String getParams(String resource)
{
int beg, f, q;
if ((beg = resource.indexOf(';')) == -1) // find params
return null;
if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
return null;
if ((q = resource.indexOf('?')) != -1 && q < beg) // find query
return null;
if (q == -1 && f == -1)
return resource.substring(beg+1);
if (f == -1 || (q != -1 && q < f))
return resource.substring(beg+1, q);
else
return resource.substring(beg+1, f);
}
/**
* Extract the query string from an http resource.
*
* @param the resource to split
* @return the query, or null if there was none
* @see #getPath
*/
public final static String getQuery(String resource)
{
int beg, f;
if ((beg = resource.indexOf('?')) == -1) // find query
return null;
if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
return null; // '?' is in fragment
if (f == -1)
return resource.substring(beg+1); // no fragment
else
return resource.substring(beg+1, f); // strip fragment
}
/**
* Extract the fragment part from an http resource.
*
* @param the resource to split
* @return the fragment, or null if there was none
* @see #getPath
*/
public final static String getFragment(String resource)
{
int beg;
if ((beg = resource.indexOf('#')) == -1) // find fragment
return null;
else
return resource.substring(beg+1);
}
/**
* Match <var>pattern</var> against <var>name</var>, where
* <var>pattern</var> may contain wildcards ('*').
*
* @param pattern the pattern to match; may contain '*' which match
* any number (0 or more) of any character (think file
* globbing)
* @param name the name to match against the pattern
* @return true if the name matches the pattern; false otherwise
*/
public static final boolean wildcardMatch(String pattern, String name)
{
return
wildcardMatch(pattern, name, 0, 0, pattern.length(), name.length());
}
private static final boolean wildcardMatch(String pattern, String name,
int ppos, int npos, int plen,
int nlen)
{
// find wildcard
int star = pattern.indexOf('*', ppos);
if (star < 0)
{
return ((plen-ppos) == (nlen-npos) &&
pattern.regionMatches(ppos, name, npos, plen-ppos));
}
// match prefix
if (!pattern.regionMatches(ppos, name, npos, star-ppos))
return false;
// match suffix
if (star == plen-1)
return true;
while(!wildcardMatch(pattern, name, star+1, npos, plen, nlen) &&
npos < nlen)
npos++;
return (npos < nlen);
}
}