/* * @(#)HTTPConnection.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.io.OutputStream; import java.io.DataOutputStream; import java.io.FilterOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.net.URL; import java.net.Socket; import java.net.InetAddress; import java.net.SocketException; import java.net.ConnectException; import java.net.UnknownHostException; import java.net.NoRouteToHostException; import java.util.Vector; import java.applet.Applet; /** * This class implements http protocol requests; it contains most of HTTP/1.1 * and ought to be unconditionally compliant. Redirections are automatically * handled, and authorizations requests are recognized and dealt with via an * authorization handler. Only full HTTP/1.0 and HTTP/1.1 requests are * generated. HTTP/1.1, HTTP/1.0 and HTTP/0.9 responses are recognized.
*
* Using the HTTPClient should be quite simple. First add the import statement '
* import HTTPClient.*;
' to your file(s). Request can then be sent
* using one of the methods Head() , Get() , Post()
* , etc in HTTPConnection . These methods all return an
* instance of HTTPResponse which has methods for accessing the
* response headers (getHeader() , getHeaderAsInt() ,
* etc), various response info (getStatusCode() ,
* getReasonLine() , etc) and the reponse data (getData() ,
* getText() , and getInputStream() ). Following are some
* examples.
* * If this is in an applet you can retrieve files from your server as follows: *
* try * { * HTTPConnection con = new HTTPConnection(this); * HTTPResponse rsp = con.Get("/my_file"); * if (rsp.getStatusCode() >= 300) * { * System.err.println("Received Error: "+rsp.getReasonLine()); * System.err.println(rsp.getText()); * } * else * data = rsp.getData(); * * rsp = con.Get("/another_file"); * if (rsp.getStatusCode() >= 300) * { * System.err.println("Received Error: "+rsp.getReasonLine()); * System.err.println(rsp.getText()); * } * else * other_data = rsp.getData(); * } * catch (IOException ioe) * { * System.err.println(ioe.toString()); * } * catch (ModuleException me) * { * System.err.println("Error handling request: " + me.getMessage()); * } *This will get the files "/my_file" and "/another_file" and put their * contents into byte[]'s accessible via
getData()
. Note that you
* need to only create a new HTTPConnection when sending a request to
* a new server (different host or port); although you may create a new
* HTTPConnection for every request to the same server this not
* recommended, as various information about the server is cached
* after the first request (to optimize subsequent requests) and persistent
* connections are used whenever possible. * * To POST form data you would use something like this (assuming you have two * fields called name and e-mail , whose contents are * stored in the variables name and email ):
* try * { * NVPair form_data[] = new NVPair[2]; * form_data[0] = new NVPair("name", name); * form_data[1] = new NVPair("e-mail", email); * * HTTPConnection con = new HTTPConnection(this); * HTTPResponse rsp = con.Post("/cgi-bin/my_script", form_data); * if (rsp.getStatusCode() >= 300) * { * System.err.println("Received Error: "+rsp.getReasonLine()); * System.err.println(rsp.getText()); * } * else * stream = rsp.getInputStream(); * } * catch (IOException ioe) * { * System.err.println(ioe.toString()); * } * catch (ModuleException me) * { * System.err.println("Error handling request: " + me.getMessage()); * } *Here the response data is read at leasure via an InputStream * instead of all at once into a byte[] .
* * As another example, if you have a URL you're trying to send a request to you * would do something like the following:
* try * { * URL url = new URL("http://www.mydomain.us/test/my_file"); * HTTPConnection con = new HTTPConnection(url); * HTTPResponse rsp = con.Put(url.getFile(), "Hello World"); * if (rsp.getStatusCode() >= 300) * { * System.err.println("Received Error: "+rsp.getReasonLine()); * System.err.println(rsp.getText()); * } * else * text = rsp.getText(); * } * catch (IOException ioe) * { * System.err.println(ioe.toString()); * } * catch (ModuleException me) * { * System.err.println("Error handling request: " + me.getMessage()); * } *
* * There are a whole number of methods for each request type; however the * general forms are ([...] means that the enclosed is optional): *
*
* Note: there is a small window where a request method such as Get()
* may have been invoked but the request has not been built and added to the
* list. Any request in this window will not be aborted.
*
* @since V0.2-3
*/
public void stop()
{
for (Request req = (Request) RequestList.enumerate(); req != null;
req = (Request) RequestList.next())
{
req.aborted = true;
}
for (StreamDemultiplexor demux =
(StreamDemultiplexor) DemuxList.enumerate();
demux != null; demux = (StreamDemultiplexor) DemuxList.next())
{
demux.abort();
}
}
/**
* Sets the default http headers to be sent with each request. The actual
* headers sent are determined as follows: for each header specified in
* multiple places a value given as part of the request takes priority over
* any default values set by this method, which in turn takes priority over
* any built-in default values. A different way of looking at it is that we
* start off with a list of all headers specified with the request, then add
* any default headers set by this method which aren't already in our list,
* and finally add any built-in headers which aren't yet in the list. There
* is one exception to this rule: the "Content-length" header is always
* ignored; and when posting form-data any default "Content-type" is ignored
* in favor of the built-in "application/x-www-form-urlencoded" (however it
* will be overriden by any content-type header specified as part of the
* request).
* * Typical headers you might want to set here are "Accept" and its * "Accept-*" relatives, "Connection", "From", "User-Agent", etc. * * @param headers an array of header-name/value pairs (do not give the * separating ':'). */ public void setDefaultHeaders(NVPair[] headers) { int length = (headers == null ? 0 : headers.length); NVPair[] def_hdrs = new NVPair[length]; // weed out undesired headers int sidx; // weed out undesired headers int didx; for (sidx = 0, didx = 0; sidx < length; sidx++) { if (headers[sidx] == null) { continue; } String name = headers[sidx].getName().trim(); if (name.equalsIgnoreCase("Content-length")) { continue; } def_hdrs[didx++] = headers[sidx]; } if (didx < length) { def_hdrs = Util.resizeArray(def_hdrs, didx); } synchronized (DefaultHeaders) { DefaultHeaders = def_hdrs; } } /** * Gets the current list of default http headers. * * @return an array of header/value pairs. */ public NVPair[] getDefaultHeaders() { synchronized (DefaultHeaders) { return (NVPair[]) DefaultHeaders.clone(); } } /** * Returns the protocol this connection is talking. * * @return a string containing the (lowercased) protocol */ public String getProtocol() { switch (Protocol) { case HTTP: return "http"; case HTTPS: return "https"; case SHTTP: return "shttp"; case HTTP_NG: return "http-ng"; default: throw new Error("HTTPClient Internal Error: invalid protocol " + Protocol); } } /** * Returns the host this connection is talking to. * * @return a string containing the (lowercased) host name. */ public String getHost() { return Host; } /** * Returns the port this connection connects to. This is always the actual * port number, never -1. * * @return the port number */ public int getPort() { return Port; } /** * Returns the host of the proxy this connection is using. * * @return a string containing the (lowercased) host name. */ public String getProxyHost() { return Proxy_Host; } /** * Returns the port of the proxy this connection is using. * * @return the port number */ public int getProxyPort() { return Proxy_Port; } /** * See if the given uri is compatible with this connection. Compatible means * that the given uri can be retrieved using this connection object. * * @param uri the URI to check * @return true if they're compatible, false otherwise * @since V0.3-2 */ public boolean isCompatibleWith(URI uri) { if (!uri.getScheme().equals(getProtocol()) || !uri.getHost().equalsIgnoreCase(Host)) { return false; } int port = uri.getPort(); if (port == -1) { port = URI.defaultPort(uri.getScheme()); } return port == Port; } /** * Sets/Resets raw mode. In raw mode all modules are bypassed, meaning the * automatic handling of authorization requests, redirections, cookies, etc. * is turned off.
*
* The default is false.
*
* @param raw if true removes all modules (except for the retry module)
* @deprecated This is not really needed anymore; in V0.2 request were
* synchronous and therefore to do pipelining you needed to disable the
* processing of responses.
* @see #removeModule(java.lang.Class)
*/
public void setRawMode(boolean raw)
{
// Don't remove the retry module
String[] modules = {"HTTPClient.CookieModule",
"HTTPClient.RedirectionModule",
"HTTPClient.AuthorizationModule",
"HTTPClient.DefaultModule",
"HTTPClient.TransferEncodingModule",
"HTTPClient.ContentMD5Module",
"HTTPClient.ContentEncodingModule"};
for (int idx = 0; idx < modules.length; idx++)
{
try
{
if (raw)
{
removeModule(Class.forName(modules[idx]));
}
else
{
addModule(Class.forName(modules[idx]), -1);
}
}
catch (ClassNotFoundException cnfe)
{
}
}
}
/**
* Sets the default timeout value to be used for each new HTTPConnection.
* The default is 0.
*
* @param time the timeout in milliseconds.
* @see #setTimeout(int)
*/
public static void setDefaultTimeout(int time)
{
DefaultTimeout = time;
}
/**
* Gets the default timeout value to be used for each new HTTPConnection.
*
* @return the timeout in milliseconds.
* @see #setTimeout(int)
*/
public static int getDefaultTimeout()
{
return DefaultTimeout;
}
/**
* Sets the timeout to be used for creating connections and reading
* responses. When a timeout expires the operation will throw an
* InterruptedIOException. The operation may be restarted again afterwards.
* If the operation is not restarted and it is a read operation (i.e
* HTTPResponse.xxxx()) then resp.getInputStream().close()
* should be invoked.
* * When creating new sockets the timeout will limit the time spent doing the * host name translation and establishing the connection with the server. *
* * The timeout also influences the reading of the response headers. However, * it does not specify a how long, for example, getStatusCode() may take, as * might be assumed. Instead it specifies how long a read on the socket may * take. If the response dribbles in slowly with packets arriving quicker * than the timeout then the method will complete normally. I.e. the * exception is only thrown if nothing arrives on the socket for the * specified time. Furthermore, the timeout only influences the reading of * the headers, not the reading of the body.
* * Read Timeouts are associated with responses, so that you may change this * value before each request and it won't affect the reading of responses to * previous requests. * * @param time the time in milliseconds. A time of 0 means wait * indefinitely. * @see #stop() */ public void setTimeout(int time) { Timeout = time; } /** * Gets the timeout used for reading response data. * * @return the current timeout value * @see #setTimeout(int) */ public int getTimeout() { return Timeout; } /** * Controls whether modules are allowed to prompt the user or pop up dialogs * if neccessary. * * @param allow if true allows modules to interact with user. */ public void setAllowUserInteraction(boolean allow) { allowUI = allow; } /** * returns whether modules are allowed to prompt or popup dialogs if * neccessary. * * @return true if modules are allowed to interact with user. */ public boolean getAllowUserInteraction() { return allowUI; } /** * Sets the default allow-user-action. * * @param allow if true allows modules to interact with user. */ public static void setDefaultAllowUserInteraction(boolean allow) { defaultAllowUI = allow; } /** * Gets the default allow-user-action. * * @return true if modules are allowed to interact with user. */ public static boolean getDefaultAllowUserInteraction() { return defaultAllowUI; } /** * Returns the default list of modules. * * @return an array of classes */ public static Class[] getDefaultModules() { return getModules(DefaultModuleList); } /** * Adds a module to the default list. It must implement the * HTTPClientModule interface. If the module is already in the list * then this method does nothing. This method only affects instances of * HTTPConnection created after this method has been invoked; it does not * affect existing instances.
* * Example:
* HTTPConnection.addDefaultModule(Class.forName("HTTPClient.CookieModule"), 1); *adds the cookie module as the second module in the list.
* * The default list is created at class initialization time from the * property HTTPClient.Modules . This must contain a "|" * separated list of classes in the order they're to be invoked. If this * property is not set it defaults to: "HTTPClient.RetryModule | * HTTPClient.CookieModule | HTTPClient.RedirectionModule | * HTTPClient.AuthorizationModule | HTTPClient.DefaultModule | * HTTPClient.TransferEncodingModule | HTTPClient.ContentMD5Module | * HTTPClient.ContentEncodingModule" * * @param module the module's Class object * @param pos the position of this module in the list; if pos * >= 0 then this is the absolute position in the list (0 is the first * position); if pos < 0 then this is the position relative * to the end of the list (-1 means the last element, -2 the second to * last element, etc). * @return true if module was successfully added; false if the module * is already in the list. * @see HTTPClientModule */ public static boolean addDefaultModule(Class module, int pos) { return addModule(DefaultModuleList, module, pos); } /** * Removes a module from the default list. If the module is not in the list * it does nothing. This method only affects instances of HTTPConnection * created after this method has been invoked; it does not affect existing * instances. * * @param module the module's Class object * @return true if module was successfully removed; false otherwise */ public static boolean removeDefaultModule(Class module) { return removeModule(DefaultModuleList, module); } /** * Returns the list of modules used currently. * * @return an array of classes */ public Class[] getModules() { return getModules(ModuleList); } /** * Adds a module to the current list. It must implement the * HTTPClientModule interface. If the module is already in the list * then this method does nothing. * * @param module the module's Class object * @param pos the position of this module in the list; if pos * >= 0 then this is the absolute position in the list (0 is the first * position); if pos < 0 then this is the position relative * to the end of the list (-1 means the last element, -2 the second to * last element, etc). * @return true if module was successfully added; false if the module * is already in the list. * @see HTTPClientModule */ public boolean addModule(Class module, int pos) { return addModule(ModuleList, module, pos); } /** * Removes a module from the current list. If the module is not in the list * it does nothing. * * @param module the module's Class object * @return true if module was successfully removed; false otherwise */ public boolean removeModule(Class module) { return removeModule(ModuleList, module); } /** * Gets the modules attribute of the HTTPConnection class * * @param list Description of the Parameter * @return The modules value */ private final static Class[] getModules(Vector list) { synchronized (list) { Class[] modules = new Class[list.size()]; list.copyInto(modules); return modules; } } /** * Adds a feature to the Module attribute of the HTTPConnection class * * @param list The feature to be added to the Module attribute * @param module The feature to be added to the Module attribute * @param pos The feature to be added to the Module attribute * @return Description of the Return Value */ private final static boolean addModule(Vector list, Class module, int pos) { if (module == null) { return false; } // check if module implements HTTPClientModule try { HTTPClientModule tmp = (HTTPClientModule) module.newInstance(); } catch (RuntimeException re) { throw re; } catch (Exception e) { throw new RuntimeException(e.toString()); } synchronized (list) { // check if module already in list if (list.contains(module)) { return false; } // add module to list if (pos < 0) { list.insertElementAt(module, DefaultModuleList.size() + pos + 1); } else { list.insertElementAt(module, pos); } } Log.write(Log.CONN, "Conn: Added module " + module.getName() + " to " + ((list == DefaultModuleList) ? "default " : "") + "list"); return true; } /** * Description of the Method * * @param list Description of the Parameter * @param module Description of the Parameter * @return Description of the Return Value */ private final static boolean removeModule(Vector list, Class module) { if (module == null) { return false; } boolean removed = list.removeElement(module); if (removed) { Log.write(Log.CONN, "Conn: Removed module " + module.getName() + " from " + ((list == DefaultModuleList) ? "default " : "") + "list"); } return removed; } /** * Sets the current context. The context is used by modules such as the * AuthorizationModule and the CookieModule which keep lists of info that is * normally shared between all instances of HTTPConnection. This is usually * the desired behaviour. However, in some cases one would like to simulate * multiple independent clients within the same application and hence the * sharing of such info should be restricted. This is where the context * comes in. Modules will only share their info between requests using the * same context (i.e. they keep multiple lists, one for each context).
*
* The context may be any object. Contexts are considered equal if equals()
* returns true. Examples of useful context objects are threads (e.g. if you
* are running multiple clients, one per thread) and sockets (e.g. if you
* are implementing a gateway).
*
* When a new HTTPConnection is created it is initialized with a default
* context which is the same for all instances. This method must be invoked
* immediately after a new HTTPConnection is created and before any request
* method is invoked. Furthermore, this method may only be called once (i.e.
* the context is "sticky").
*
* @param context the new context; must be non-null
*/
public void setContext(Object context)
{
if (context == null)
{
throw new IllegalArgumentException("Context must be non-null");
}
if (Context != null)
{
throw new IllegalStateException("Context already set");
}
Context = context;
}
/**
* Returns the current context.
*
* @return the current context, or the default context if setContext()
* hasn't been invoked
* @see #setContext(java.lang.Object)
*/
public Object getContext()
{
if (Context != null)
{
return Context;
}
else
{
return dflt_context;
}
}
/**
* Returns the default context.
*
* @return the default context
* @see #setContext(java.lang.Object)
*/
public static Object getDefaultContext()
{
return dflt_context;
}
/**
* Adds an authorization entry for the "digest" authorization scheme to the
* list. If an entry already exists for the "digest" scheme and the
* specified realm then it is overwritten.
* * This is a convenience method and just invokes the corresponding method in * AuthorizationInfo. * * @param realm the realm * @param user the username * @param passwd The feature to be added to the DigestAuthorization * attribute * @see AuthorizationInfo#addDigestAuthorization(java.lang.String, * int, java.lang.String, java.lang.String, java.lang.String) */ public void addDigestAuthorization(String realm, String user, String passwd) { AuthorizationInfo.addDigestAuthorization(Host, Port, realm, user, passwd, getContext()); } /** * Adds an authorization entry for the "basic" authorization scheme to the * list. If an entry already exists for the "basic" scheme and the specified * realm then it is overwritten.
* * This is a convenience method and just invokes the corresponding method in * AuthorizationInfo. * * @param realm the realm * @param user the username * @param passwd The feature to be added to the BasicAuthorization * attribute * @see AuthorizationInfo#addBasicAuthorization(java.lang.String, * int, java.lang.String, java.lang.String, java.lang.String) */ public void addBasicAuthorization(String realm, String user, String passwd) { AuthorizationInfo.addBasicAuthorization(Host, Port, realm, user, passwd, getContext()); } /** * Sets the default proxy server to use. The proxy will only be used for new * HTTPConnection s created after this call and will not affect * currrent instances of HTTPConnection . A null or empty string * host parameter disables the proxy.
* * In an application or using the Appletviewer an alternative to this method * is to set the following properties (either in the properties file or on * the command line): http.proxyHost and http.proxyPort * . Whether http.proxyHost is set or not determines * whether a proxy server is used.
* * If the proxy server requires authorization and you wish to set this * authorization information in the code, then you may use any of the * AuthorizationInfo.addXXXAuthorization() methods to do so. Specify * the same host and port as in this method. If you * have not given any authorization info and the proxy server requires * authorization then you will be prompted for the necessary info via a * popup the first time you do a request. * * @param host the host on which the proxy server resides. * @param port the port the proxy server is listening on. * @see #setCurrentProxy(java.lang.String, int) */ public static void setProxyServer(String host, int port) { if (host == null || host.trim().length() == 0) { Default_Proxy_Host = null; } else { Default_Proxy_Host = host.trim().toLowerCase(); Default_Proxy_Port = port; } } /** * Sets the proxy used by this instance. This can be used to override the * proxy setting inherited from the default proxy setting. A null or empty * string host parameter disables the proxy.
*
* Note that if you set a proxy for the connection using this method, and a
* request made over this connection is redirected to a different server,
* then the connection used for new server will not pick this proxy
* setting, but instead will use the default proxy settings.
*
* @param host the host the proxy runs on
* @param port the port the proxy is listening on
* @see #setProxyServer(java.lang.String, int)
*/
public synchronized void setCurrentProxy(String host, int port)
{
if (host == null || host.trim().length() == 0)
{
Proxy_Host = null;
}
else
{
Proxy_Host = host.trim().toLowerCase();
if (port <= 0)
{
Proxy_Port = 80;
}
else
{
Proxy_Port = port;
}
}
// the proxy might be talking a different version, so renegotiate
switch (Protocol)
{
case HTTP:
case HTTPS:
if (force_1_0)
{
ServerProtocolVersion = HTTP_1_0;
ServProtVersKnown = true;
RequestProtocolVersion = "HTTP/1.0";
}
else
{
ServerProtocolVersion = HTTP_1_1;
ServProtVersKnown = false;
RequestProtocolVersion = "HTTP/1.1";
}
break;
case HTTP_NG:
ServerProtocolVersion = -1;
/*
* Unknown
*/
ServProtVersKnown = false;
RequestProtocolVersion = "";
break;
case SHTTP:
ServerProtocolVersion = -1;
/*
* Unknown
*/
ServProtVersKnown = false;
RequestProtocolVersion = "Secure-HTTP/1.3";
break;
default:
throw new Error("HTTPClient Internal Error: invalid protocol " +
Protocol);
}
keepAliveUnknown = true;
doesKeepAlive = false;
input_demux = null;
early_stall = null;
late_stall = null;
prev_resp = null;
}
/**
* Add host to the list of hosts which should be accessed
* directly, not via any proxy set by setProxyServer()
.
* * The host may be any of: *
*
* The two properties HTTPClient.nonProxyHosts and
* http.nonProxyHosts are used when this class is loaded to initialize
* the list of non-proxy hosts. The second property is only read if the
* first one is not set; the second property is also used the JDK's
* URLConnection. These properties must contain a "|" separated list of
* entries which conform to the above rules for the host
* parameter (e.g. "11.22.33.44|.disney.com").
*
* @param host a host name, domain name, IP-address or
* IP-subnet.
* @exception ParseException if the length of the netmask does not match
* the length of the IP-address
*/
public static void dontProxyFor(String host)
throws ParseException
{
host = host.trim().toLowerCase();
// check for domain name
if (host.charAt(0) == '.')
{
if (!non_proxy_dom_list.contains(host))
{
non_proxy_dom_list.addElement(host);
}
return;
}
// check for host name
for (int idx = 0; idx < host.length(); idx++)
{
if (!Character.isDigit(host.charAt(idx)) &&
host.charAt(idx) != '.' && host.charAt(idx) != '/')
{
non_proxy_host_list.put(host, "");
return;
}
}
// must be an IP-address
byte[] ip_addr;
byte[] ip_mask;
int slash;
if ((slash = host.indexOf('/')) != -1)
{
// IP subnet
ip_addr = string2arr(host.substring(0, slash));
ip_mask = string2arr(host.substring(slash + 1));
if (ip_addr.length != ip_mask.length)
{
throw new ParseException("length of IP-address (" +
ip_addr.length + ") != length of netmask (" +
ip_mask.length + ")");
}
}
else
{
ip_addr = string2arr(host);
ip_mask = new byte[ip_addr.length];
for (int idx = 0; idx < ip_mask.length; idx++)
{
ip_mask[idx] = (byte) 255;
}
}
// check if addr or subnet already exists
ip_loop :
for (int idx = 0; idx < non_proxy_addr_list.size(); idx++)
{
byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
if (addr.length != ip_addr.length)
{
continue;
}
for (int idx2 = 0; idx2 < addr.length; idx2++)
{
if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2]) ||
(mask[idx2] != ip_mask[idx2]))
{
continue ip_loop;
}
}
return;
// already exists
}
non_proxy_addr_list.addElement(ip_addr);
non_proxy_mask_list.addElement(ip_mask);
}
/**
* Convenience method to add a number of hosts at once. If any one host is
* null or cannot be parsed it is ignored.
*
* @param hosts The list of hosts to set
* @see #dontProxyFor(java.lang.String)
* @since V0.3-2
*/
public static void dontProxyFor(String[] hosts)
{
if (hosts == null || hosts.length == 0)
{
return;
}
for (int idx = 0; idx < hosts.length; idx++)
{
try
{
if (hosts[idx] != null)
{
dontProxyFor(hosts[idx]);
}
}
catch (ParseException pe)
{
// ignore it
}
}
}
/**
* Remove host from the list of hosts for which the proxy should
* not be used. This modifies the same list that dontProxyFor()
* uses, i.e. this is used to undo a dontProxyFor()
setting.
* The syntax for host is specified in dontProxyFor()
* .
*
* @param host a host name, domain name, IP-address or
* IP-subnet.
* @return true if the remove was sucessful, false
* otherwise
* @exception ParseException if the length of the netmask does not match
* the length of the IP-address
* @see #dontProxyFor(java.lang.String)
*/
public static boolean doProxyFor(String host)
throws ParseException
{
host = host.trim().toLowerCase();
// check for domain name
if (host.charAt(0) == '.')
{
return non_proxy_dom_list.removeElement(host);
}
// check for host name
for (int idx = 0; idx < host.length(); idx++)
{
if (!Character.isDigit(host.charAt(idx)) &&
host.charAt(idx) != '.' && host.charAt(idx) != '/')
{
return (non_proxy_host_list.remove(host) != null);
}
}
// must be an IP-address
byte[] ip_addr;
byte[] ip_mask;
int slash;
if ((slash = host.indexOf('/')) != -1)
{
// IP subnet
ip_addr = string2arr(host.substring(0, slash));
ip_mask = string2arr(host.substring(slash + 1));
if (ip_addr.length != ip_mask.length)
{
throw new ParseException("length of IP-address (" +
ip_addr.length + ") != length of netmask (" +
ip_mask.length + ")");
}
}
else
{
ip_addr = string2arr(host);
ip_mask = new byte[ip_addr.length];
for (int idx = 0; idx < ip_mask.length; idx++)
{
ip_mask[idx] = (byte) 255;
}
}
ip_loop :
for (int idx = 0; idx < non_proxy_addr_list.size(); idx++)
{
byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
if (addr.length != ip_addr.length)
{
continue;
}
for (int idx2 = 0; idx2 < addr.length; idx2++)
{
if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2]) ||
(mask[idx2] != ip_mask[idx2]))
{
continue ip_loop;
}
}
non_proxy_addr_list.removeElementAt(idx);
non_proxy_mask_list.removeElementAt(idx);
return true;
}
return false;
}
/**
* Turn an IP-address string into an array (e.g. "12.34.56.78" into { 12,
* 34, 56, 78 }).
*
* @param ip IP-address
* @return IP-address in network byte order
*/
private static byte[] string2arr(String ip)
{
byte[] arr;
char[] ip_char = new char[ip.length()];
ip.getChars(0, ip_char.length, ip_char, 0);
int cnt = 0;
for (int idx = 0; idx < ip_char.length; idx++)
{
if (ip_char[idx] == '.')
{
cnt++;
}
}
arr = new byte[cnt + 1];
cnt = 0;
int pos = 0;
for (int idx = 0; idx < ip_char.length; idx++)
{
if (ip_char[idx] == '.')
{
arr[cnt] = (byte) Integer.parseInt(ip.substring(pos, idx));
cnt++;
pos = idx + 1;
}
}
arr[cnt] = (byte) Integer.parseInt(ip.substring(pos));
return arr;
}
/**
* Sets the SOCKS server to use. The server will only be used for new
* HTTPConnections created after this call and will not affect currrent
* instances of HTTPConnection. A null or empty string host parameter
* disables SOCKS.
* * The code will try to determine the SOCKS version to use at connection * time. This might fail for a number of reasons, however, in which case you * must specify the version explicitly. * * @param host the host on which the proxy server resides. The port used is * the default port 1080. * @see #setSocksServer(java.lang.String, int, int) */ public static void setSocksServer(String host) { setSocksServer(host, 1080); } /** * Sets the SOCKS server to use. The server will only be used for new * HTTPConnections created after this call and will not affect currrent * instances of HTTPConnection. A null or empty string host parameter * disables SOCKS.
* * The code will try to determine the SOCKS version to use at connection * time. This might fail for a number of reasons, however, in which case you * must specify the version explicitly. * * @param host the host on which the proxy server resides. * @param port the port the proxy server is listening on. * @see #setSocksServer(java.lang.String, int, int) */ public static void setSocksServer(String host, int port) { if (port <= 0) { port = 1080; } if (host == null || host.length() == 0) { Default_Socks_client = null; } else { Default_Socks_client = new SocksClient(host, port); } } /** * Sets the SOCKS server to use. The server will only be used for new * HTTPConnections created after this call and will not affect currrent * instances of HTTPConnection. A null or empty string host parameter * disables SOCKS.
* * In an application or using the Appletviewer an alternative to this method * is to set the following properties (either in the properties file or on * the command line): HTTPClient.socksHost , * HTTPClient.socksPort and HTTPClient.socksVersion . * Whether HTTPClient.socksHost is set or not determines whether * a SOCKS server is used; if HTTPClient.socksPort is not set it * defaults to 1080; if HTTPClient.socksVersion is not set an * attempt will be made to automatically determine the version used by the * server.
* * Note: If you have also set a proxy server then a connection will be made * to the SOCKS server, which in turn then makes a connection to the proxy * server (possibly via other SOCKS servers), which in turn makes the final * connection.
* * If the proxy server is running SOCKS version 5 and requires * username/password authorization, and you wish to set this authorization * information in the code, then you may use the * AuthorizationInfo.addAuthorization() method to do so. Specify the * same host and port as in this method, give the * scheme "SOCKS5" and the realm "USER/PASS", set the * cookie to null and the params to an array * containing a single NVPair in turn containing the username and * password. Example:
* NVPair[] up = { new NVPair(username, password) }; * AuthorizationInfo.addAuthorization(host, port, "SOCKS5", "USER/PASS", * null, up); *If you have not given any authorization info and the proxy server * requires authorization then you will be prompted for the necessary info * via a popup the first time you do a request. * * @param host the host on which the proxy server resides. * @param port the port the proxy server is listening on. * @param version the SOCKS version the server is running. * Currently this must be '4' or '5'. * @exception SocksException If version is not '4' or '5'. */ public static void setSocksServer(String host, int port, int version) throws SocksException { if (port <= 0) { port = 1080; } if (host == null || host.length() == 0) { Default_Socks_client = null; } else { Default_Socks_client = new SocksClient(host, port, version); } } /** * Removes the #... part. Returns the stripped name, or "" if either the * file is null or is the empty string (after stripping). * * @param file the name to strip * @return the stripped name */ private final String stripRef(String file) { if (file == null) { return ""; } int hash = file.indexOf('#'); if (hash != -1) { file = file.substring(0, hash); } return file.trim(); } // private helper methods /** * Sets up the request, creating the list of headers to send and creating * instances of the modules. This may be invoked by subclasses which add * further methods (such as those from DAV and IPP). * * @param method GET, POST, etc. * @param resource the resource * @param headers an array of headers to be used * @param entity the entity (or null) * @param stream the output stream (or null) - only one of * stream and entity may be non-null * @return the response. * @exception ModuleException if an exception is encountered in any module. * @exception IOException Description of the Exception */ protected final HTTPResponse setupRequest(String method, String resource, NVPair[] headers, byte[] entity, HttpOutputStream stream) throws IOException, ModuleException { Request req = new Request(this, method, resource, mergedHeaders(headers), entity, stream, allowUI); RequestList.addToEnd(req); try { HTTPResponse resp = new HTTPResponse(gen_mod_insts(), Timeout, req, defaultIncrement); handleRequest(req, resp, null, true); return resp; } finally { RequestList.remove(req); } } /** * This merges built-in default headers, user-specified default headers, and * method-specified headers. Method-specified take precedence over user * defaults, which take precedence over built-in defaults. The following * headers are removed if found: "Content-length". * * @param spec the headers specified in the call to the method * @return an array consisting of merged headers. */ private NVPair[] mergedHeaders(NVPair[] spec) { int spec_len = (spec != null ? spec.length : 0); int defs_len; NVPair[] merged; synchronized (DefaultHeaders) { defs_len = (DefaultHeaders != null ? DefaultHeaders.length : 0); merged = new NVPair[spec_len + defs_len]; // copy default headers System.arraycopy(DefaultHeaders, 0, merged, 0, defs_len); } // merge in selected headers int sidx; // merge in selected headers int didx = defs_len; for (sidx = 0; sidx < spec_len; sidx++) { if (spec[sidx] == null) { continue; } String s_name = spec[sidx].getName().trim(); if (s_name.equalsIgnoreCase("Content-length")) { continue; } int search; for (search = 0; search < didx; search++) { if (merged[search].getName().trim().equalsIgnoreCase(s_name)) { break; } } merged[search] = spec[sidx]; if (search == didx) { didx++; } } if (didx < merged.length) { merged = Util.resizeArray(merged, didx); } return merged; } /** * Generate an array of instances of the current modules. * * @return Description of the Return Value */ private HTTPClientModule[] gen_mod_insts() { synchronized (ModuleList) { HTTPClientModule[] mod_insts = new HTTPClientModule[ModuleList.size()]; for (int idx = 0; idx < ModuleList.size(); idx++) { Class mod = (Class) ModuleList.elementAt(idx); try { mod_insts[idx] = (HTTPClientModule) mod.newInstance(); } catch (Exception e) { throw new Error("HTTPClient Internal Error: could not " + "create instance of " + mod.getName() + " -\n" + e); } } return mod_insts; } } /** * handles the Request. First the request handler for each module is is * invoked, and then if no response was generated the request is sent. * * @param req the Request * @param http_resp the HTTPResponse * @param resp the Response * @param usemodules if false then skip module loop * @exception IOException if any module or sendRequest throws it * @exception ModuleException if any module throws it */ void handleRequest(Request req, HTTPResponse http_resp, Response resp, boolean usemodules) throws IOException, ModuleException { Response[] rsp_arr = {resp}; HTTPClientModule[] modules = http_resp.getModules(); // invoke requestHandler for each module if (usemodules) { doModules : for (int idx = 0; idx < modules.length; idx++) { int sts = modules[idx].requestHandler(req, rsp_arr); switch (sts) { case REQ_CONTINUE: // continue processing break; case REQ_RESTART: // restart processing with first module idx = -1; continue doModules; case REQ_SHORTCIRC: // stop processing and send break doModules; case REQ_RESPONSE: // go to phase 2 case REQ_RETURN: // return response immediately if (rsp_arr[0] == null) { throw new Error("HTTPClient Internal Error: no " + "response returned by module " + modules[idx].getClass().getName()); } http_resp.set(req, rsp_arr[0]); if (req.getStream() != null) { req.getStream().ignoreData(req); } if (req.internal_subrequest) { return; } if (sts == REQ_RESPONSE) { http_resp.handleResponse(); } else { http_resp.init(rsp_arr[0]); } return; case REQ_NEWCON_RST: // new connection if (req.internal_subrequest) { return; } req.getConnection(). handleRequest(req, http_resp, rsp_arr[0], true); return; case REQ_NEWCON_SND: // new connection, send immediately if (req.internal_subrequest) { return; } req.getConnection(). handleRequest(req, http_resp, rsp_arr[0], false); return; default: // not valid throw new Error("HTTPClient Internal Error: invalid status" + " " + sts + " returned by module " + modules[idx].getClass().getName()); } } } if (req.internal_subrequest) { return; } // Send the request across the wire if (req.getStream() != null && req.getStream().getLength() == -1) { if (!ServProtVersKnown || ServerProtocolVersion < HTTP_1_1 || no_chunked) { req.getStream().goAhead(req, null, http_resp.getTimeout()); http_resp.set(req, req.getStream()); } else { // add Transfer-Encoding header if necessary int idx; NVPair[] hdrs = req.getHeaders(); for (idx = 0; idx < hdrs.length; idx++) { if (hdrs[idx].getName().equalsIgnoreCase("Transfer-Encoding")) { break; } } if (idx == hdrs.length) { hdrs = Util.resizeArray(hdrs, idx + 1); hdrs[idx] = new NVPair("Transfer-Encoding", "chunked"); req.setHeaders(hdrs); } else { String v = hdrs[idx].getValue(); try { if (!Util.hasToken(v, "chunked")) { hdrs[idx] = new NVPair("Transfer-Encoding", v + ", chunked"); } } catch (ParseException pe) { throw new IOException(pe.toString()); } } http_resp.set(req, sendRequest(req, http_resp.getTimeout())); } } else { http_resp.set(req, sendRequest(req, http_resp.getTimeout())); } if (req.aborted) { throw new IOException("Request aborted by user"); } } /** * These mark the response to stall the next request on, if any */ private volatile Response early_stall = null; private volatile Response late_stall = null; private volatile Response prev_resp = null; /** * This marks the socket output stream as still being used */ private boolean output_finished = true; /** * sends the request over the line. * * @param req the request * @param con_timeout the timeout to use when establishing a socket * connection; an InterruptedIOException is thrown if the procedure * times out. * @return Description of the Return Value * @exception IOException if thrown by the socket * @exception ModuleException if any module throws it during the SSL- * tunneling handshake */ Response sendRequest(Request req, int con_timeout) throws IOException, ModuleException { ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600); Response resp = null; boolean keep_alive; // The very first request is special in that we need its response // before any further requests may be made. This is to set things // like the server version. if (early_stall != null) { try { Log.write(Log.CONN, "Conn: Early-stalling Request: " + req.getMethod() + " " + req.getRequestURI()); synchronized (early_stall) { // wait till the response is received try { early_stall.getVersion(); } catch (IOException ioe) { } early_stall = null; } } catch (NullPointerException npe) { } } String[] con_hdrs = assembleHeaders(req, hdr_buf); // determine if the connection should be kept alive after this // request try { if (ServerProtocolVersion >= HTTP_1_1 && !Util.hasToken(con_hdrs[0], "close") || ServerProtocolVersion == HTTP_1_0 && Util.hasToken(con_hdrs[0], "keep-alive") ) { keep_alive = true; } else { keep_alive = false; } } catch (ParseException pe) { throw new IOException(pe.toString()); } synchronized (this) { // Sometimes we must stall the pipeline until the previous request // has been answered. However, if we are going to open up a new // connection anyway we don't really need to stall. if (late_stall != null) { if (input_demux != null || keepAliveUnknown) { Log.write(Log.CONN, "Conn: Stalling Request: " + req.getMethod() + " " + req.getRequestURI()); try { // wait till the response is received late_stall.getVersion(); if (keepAliveUnknown) { determineKeepAlive(late_stall); } } catch (IOException ioe) { } } late_stall = null; } /* * POSTs must not be pipelined because of problems if the connection * is aborted. Since it is generally impossible to know what urls * POST will influence it is impossible to determine if a sequence * of requests containing a POST is idempotent. * Also, for retried requests we don't want to pipeline either. */ if ((req.getMethod().equals("POST") || req.dont_pipeline) && prev_resp != null && input_demux != null) { Log.write(Log.CONN, "Conn: Stalling Request: " + req.getMethod() + " " + req.getRequestURI()); try { // wait till the response is received prev_resp.getVersion(); } catch (IOException ioe) { } } // If the previous request used an output stream, then wait till // all the data has been written if (!output_finished) { try { wait(); } catch (InterruptedException ie) { throw new IOException(ie.toString()); } } if (req.aborted) { throw new IOException("Request aborted by user"); } int try_count = 3; /* * what a hack! This is to handle the case where the server closes * the connection but we don't realize it until we try to send * something. The problem is that we only get IOException, but * we need a finer specification (i.e. whether it's an EPIPE or * something else); I don't trust relying on the message part * of IOException (which on SunOS/Solaris gives 'Broken pipe', * but what on Windoze/Mac?). */ while (try_count-- > 0) { try { // get a client socket Socket sock; if (input_demux == null || (sock = input_demux.getSocket()) == null) { sock = getSocket(con_timeout); if (Protocol == HTTPS) { if (Proxy_Host != null) { Socket[] sarr = {sock}; resp = enableSSLTunneling(sarr, req, con_timeout); if (resp != null) { resp.final_resp = true; return resp; } sock = sarr[0]; } sock.setSoTimeout(con_timeout); //sock = new SSLSocket(sock); } input_demux = new StreamDemultiplexor(Protocol, sock, this); DemuxList.addToEnd(input_demux); keepAliveReqLeft = keepAliveReqMax; } if (req.aborted) { throw new IOException("Request aborted by user"); } Log.write(Log.CONN, "Conn: Sending Request: ", hdr_buf); // Send headers OutputStream sock_out = sock.getOutputStream(); if (haveMSLargeWritesBug) { sock_out = new MSLargeWritesBugStream(sock_out); } hdr_buf.writeTo(sock_out); // Wait for "100 Continue" status if necessary try { if (ServProtVersKnown && ServerProtocolVersion >= HTTP_1_1 && Util.hasToken(con_hdrs[1], "100-continue")) { resp = new Response(req, (Proxy_Host != null && Protocol != HTTPS), input_demux); resp.timeout = 60; if (resp.getContinue() != 100) { break; } } } catch (ParseException pe) { throw new IOException(pe.toString()); } catch (InterruptedIOException iioe) { } finally { if (resp != null) { resp.timeout = 0; } } // POST/PUT data if (req.getData() != null && req.getData().length > 0) { if (req.delay_entity > 0) { // wait for something on the network; check available() // roughly every 100 ms long num_units = req.delay_entity / 100; long one_unit = req.delay_entity / num_units; for (int idx = 0; idx < num_units; idx++) { if (input_demux.available(null) != 0) { break; } try { Thread.sleep(one_unit); } catch (InterruptedException ie) { } } if (input_demux.available(null) == 0) { sock_out.write(req.getData()); } // he's still waiting else { keep_alive = false; } // Uh oh! } else { sock_out.write(req.getData()); } } if (req.getStream() != null) { req.getStream().goAhead(req, sock_out, 0); } else { sock_out.flush(); } // get a new response. // Note: this does not do a read on the socket. if (resp == null) { resp = new Response(req, (Proxy_Host != null && Protocol != HTTPS), input_demux); } } catch (IOException ioe) { Log.write(Log.CONN, "Conn: ", ioe); closeDemux(ioe, true); if (try_count == 0 || ioe instanceof UnknownHostException || ioe instanceof ConnectException || ioe instanceof NoRouteToHostException || ioe instanceof InterruptedIOException || req.aborted) { throw ioe; } Log.write(Log.CONN, "Conn: Retrying request"); continue; } break; } prev_resp = resp; // close the stream after this response if necessary if ((!keepAliveUnknown && !doesKeepAlive) || !keep_alive || (keepAliveReqMax != -1 && keepAliveReqLeft-- == 0)) { input_demux.markForClose(resp); input_demux = null; } else { input_demux.restartTimer(); } if (keepAliveReqMax != -1) { Log.write(Log.CONN, "Conn: Number of requests left: " + keepAliveReqLeft); } /* * We don't pipeline the first request, as we need some info * about the server (such as which http version it complies with) */ if (!ServProtVersKnown) { early_stall = resp; resp.markAsFirstResponse(req); } /* * Also don't pipeline until we know if the server supports * keep-alive's or not. * Note: strictly speaking, HTTP/1.0 keep-alives don't mean we can * pipeline requests. I seem to remember some (beta?) version * of Netscape's Enterprise server which barfed if you tried * push requests down it's throat w/o waiting for the previous * response first. However, I've not been able to find such a * server lately, and so I'm taking the risk and assuming we * can in fact pipeline requests to HTTP/1.0 servers. */ if (keepAliveUnknown || // We don't pipeline POST's ... !IdempotentSequence.methodIsIdempotent(req.getMethod()) || req.dont_pipeline || // Retries disable pipelining too neverPipeline) { // Emergency measure: prevent all pipelining late_stall = resp; } /* * If there is an output stream then just tell the other threads to * wait; the stream will notify() when it's done. If there isn't any * stream then wake up a waiting thread (if any). */ if (req.getStream() != null) { output_finished = false; } else { output_finished = true; notify(); } // Looks like were finally done Log.write(Log.CONN, "Conn: Request sent"); } return resp; } /** * Gets a socket. Creates a socket to the proxy if set, or else to the * actual destination. * * @param con_timeout if not 0 then start a new thread to establish the * the connection and join(con_timeout) it. If the join() times out an * InteruptedIOException is thrown. * @return The socket value * @exception IOException Description of the Exception */ private Socket getSocket(int con_timeout) throws IOException { Socket sock = null; String actual_host; int actual_port; if (Proxy_Host != null) { actual_host = Proxy_Host; actual_port = Proxy_Port; } else { actual_host = Host; actual_port = Port; } Log.write(Log.CONN, "Conn: Creating Socket: " + actual_host + ":" + actual_port); if (con_timeout == 0) { // normal connection establishment if (Socks_client != null) { sock = Socks_client.getSocket(actual_host, actual_port); } else { // try all A records InetAddress[] addr_list = InetAddress.getAllByName(actual_host); for (int idx = 0; idx < addr_list.length; idx++) { try { if (LocalAddr == null) { sock = new Socket(addr_list[idx], actual_port); } else { sock = new Socket(addr_list[idx], actual_port, LocalAddr, LocalPort); } break; // success } catch (SocketException se) { if (idx == addr_list.length - 1) { throw se; } // we tried them all } } } } else { EstablishConnection con = new EstablishConnection(actual_host, actual_port, Socks_client); con.start(); try { con.join((long) con_timeout); } catch (InterruptedException ie) { } if (con.getException() != null) { throw con.getException(); } if ((sock = con.getSocket()) == null) { con.forget(); if ((sock = con.getSocket()) == null) { throw new InterruptedIOException("Connection establishment timed out"); } } } return sock; } /** * Enable SSL Tunneling if we're talking to a proxy. See ietf draft * draft-luotonen-ssl-tunneling-03 for more info. * * @param sock the socket * @param req the request initiating this connection * @param timeout the timeout * @return the proxy's last response if unsuccessful, or * null if tunnel successfuly established * @exception IOException * @exception ModuleException */ private Response enableSSLTunneling(Socket[] sock, Request req, int timeout) throws IOException, ModuleException { // copy User-Agent and Proxy-Auth headers from request Vector hdrs = new Vector(); for (int idx = 0; idx < req.getHeaders().length; idx++) { String name = req.getHeaders()[idx].getName(); if (name.equalsIgnoreCase("User-Agent") || name.equalsIgnoreCase("Proxy-Authorization")) { hdrs.addElement(req.getHeaders()[idx]); } } // create initial CONNECT subrequest NVPair[] h = new NVPair[hdrs.size()]; hdrs.copyInto(h); Request connect = new Request(this, "CONNECT", Host + ":" + Port, h, null, null, req.allowUI()); connect.internal_subrequest = true; ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600); HTTPResponse r = new HTTPResponse(gen_mod_insts(), timeout, connect, defaultIncrement); // send and handle CONNECT request until successful or tired Response resp = null; while (true) { handleRequest(connect, r, resp, true); hdr_buf.reset(); assembleHeaders(connect, hdr_buf); Log.write(Log.CONN, "Conn: Sending SSL-Tunneling Subrequest: ", hdr_buf); // send CONNECT hdr_buf.writeTo(sock[0].getOutputStream()); // return if successful resp = new Response(connect, sock[0].getInputStream()); if (resp.getStatusCode() == 200) { return null; } // failed! // make life easy: read data and close socket try { resp.getData(); } catch (IOException ioe) { } try { sock[0].close(); } catch (IOException ioe) { } // handle response r.set(connect, resp); if (!r.handleResponse()) { return resp; } sock[0] = getSocket(timeout); } } /** * This writes out the headers on the hdr_buf . It takes special * precautions for the following headers: