0001: /*
0002: * @(#)HTTPConnection.java 0.3-2 18/06/1999
0003: *
0004: * This file is part of the HTTPClient package
0005: * Copyright (C) 1996-1999 Ronald Tschalär
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free
0019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
0020: * MA 02111-1307, USA
0021: *
0022: * For questions, suggestions, bug-reports, enhancement-requests etc.
0023: * I may be contacted at:
0024: *
0025: * ronald@innovation.ch
0026: *
0027: */
0028:
0029: package HTTPClient;
0030:
0031: import java.io.OutputStream;
0032: import java.io.DataOutputStream;
0033: import java.io.FilterOutputStream;
0034: import java.io.ByteArrayOutputStream;
0035: import java.io.IOException;
0036: import java.io.InterruptedIOException;
0037: import java.net.URL;
0038: import java.net.Socket;
0039: import java.net.InetAddress;
0040: import java.net.SocketException;
0041: import java.net.UnknownHostException;
0042: import java.util.Vector;
0043: import java.util.Hashtable;
0044: import java.applet.Applet;
0045:
0046: /**
0047: * This class implements http protocol requests; it contains most of HTTP/1.1
0048: * and ought to be unconditionally compliant.
0049: * Redirections are automatically handled, and authorizations requests are
0050: * recognized and dealt with via an authorization handler.
0051: * Only full HTTP/1.0 and HTTP/1.1 requests are generated. HTTP/1.1, HTTP/1.0
0052: * and HTTP/0.9 responses are recognized.
0053: *
0054: * <P>Using the HTTPClient should be quite simple. First add the import
0055: * statement '<code>import HTTPClient.*;</code>' to your file(s). Request
0056: * can then be sent using one of the methods <var>Head()</var>,
0057: * <var>Get()</var>, <var>Post()</var>, etc in <var>HTTPConnection</var>.
0058: * These methods all return an instance of <var>HTTPResponse</var> which
0059: * has methods for accessing the response headers (<var>getHeader()</var>,
0060: * <var>getHeaderAsInt()</var>, etc), various response info
0061: * (<var>getStatusCode()</var>, <var>getReasonLine()</var>, etc) and the
0062: * reponse data (<var>getData()</var> and <var>getInputStream()</var>).
0063: * Following are some examples.
0064: *
0065: * <P>If this is in an applet you can retrieve files from your server
0066: * as follows:
0067: *
0068: * <PRE>
0069: * try
0070: * {
0071: * HTTPConnection con = new HTTPConnection(this);
0072: * HTTPResponse rsp = con.Get("/my_file");
0073: * if (rsp.getStatusCode() >= 300)
0074: * {
0075: * System.err.println("Received Error: "+rsp.getReasonLine());
0076: * System.err.println(new String(rsp.getData()));
0077: * }
0078: * else
0079: * data = rsp.getData();
0080: *
0081: * rsp = con.Get("/another_file");
0082: * if (rsp.getStatusCode() >= 300)
0083: * {
0084: * System.err.println("Received Error: "+rsp.getReasonLine());
0085: * System.err.println(new String(rsp.getData()));
0086: * }
0087: * else
0088: * other_data = rsp.getData();
0089: * }
0090: * catch (IOException ioe)
0091: * {
0092: * System.err.println(ioe.toString());
0093: * }
0094: * catch (ModuleException me)
0095: * {
0096: * System.err.println("Error handling request: " + me.getMessage());
0097: * }
0098: * </PRE>
0099: *
0100: * This will get the files "/my_file" and "/another_file" and put their
0101: * contents into byte[]s accessible via <code>getData()</code>. Note that
0102: * you need to only create a new <var>HTTPConnection</var> when sending a
0103: * request to a new server (different host or port); although you may create
0104: * a new <var>HTTPConnection</var> for every request to the same server this
0105: * <strong>not</strong> recommended, as various information about the server
0106: * is cached after the first request (to optimize subsequent requests) and
0107: * persistent connections are used whenever possible.
0108: *
0109: * <P>To POST form data you would use something like this (assuming you
0110: * have two fields called <var>name</var> and <var>e-mail</var>, whose
0111: * contents are stored in the variables <var>name</var> and <var>email</var>):
0112: *
0113: * <PRE>
0114: * try
0115: * {
0116: * NVPair form_data[] = new NVPair[2];
0117: * form_data[0] = new NVPair("name", name);
0118: * form_data[1] = new NVPair("e-mail", email);
0119: *
0120: * HTTPConnection con = new HTTPConnection(this);
0121: * HTTPResponse rsp = con.Post("/cgi-bin/my_script", form_data);
0122: * if (rsp.getStatusCode() >= 300)
0123: * {
0124: * System.err.println("Received Error: "+rsp.getReasonLine());
0125: * System.err.println(new String(rsp.getData()));
0126: * }
0127: * else
0128: * stream = rsp.getInputStream();
0129: * }
0130: * catch (IOException ioe)
0131: * {
0132: * System.err.println(ioe.toString());
0133: * }
0134: * catch (ModuleException me)
0135: * {
0136: * System.err.println("Error handling request: " + me.getMessage());
0137: * }
0138: * </PRE>
0139: *
0140: * Here the response data is read at leasure via an <var>InputStream</var>
0141: * instead of all at once into a <var>byte[]</var>.
0142: *
0143: * <P>As another example, if you have a URL you're trying to send a request
0144: * to you would do something like the following:
0145: *
0146: * <PRE>
0147: * try
0148: * {
0149: * URL url = new URL("http://www.mydomain.us/test/my_file");
0150: * HTTPConnection con = new HTTPConnection(url);
0151: * HTTPResponse rsp = con.Put(url.getFile(), "Hello World");
0152: * if (rsp.getStatusCode() >= 300)
0153: * {
0154: * System.err.println("Received Error: "+rsp.getReasonLine());
0155: * System.err.println(new String(rsp.getData()));
0156: * }
0157: * else
0158: * data = rsp.getData();
0159: * }
0160: * catch (IOException ioe)
0161: * {
0162: * System.err.println(ioe.toString());
0163: * }
0164: * catch (ModuleException me)
0165: * {
0166: * System.err.println("Error handling request: " + me.getMessage());
0167: * }
0168: * </PRE>
0169: *
0170: * <P>There are a whole number of methods for each request type; however the
0171: * general forms are ([...] means that the enclosed is optional):
0172: * <ul>
0173: * <li> Head ( file [, form-data [, headers ] ] )
0174: * <li> Head ( file [, query [, headers ] ] )
0175: * <li> Get ( file [, form-data [, headers ] ] )
0176: * <li> Get ( file [, query [, headers ] ] )
0177: * <li> Post ( file [, form-data [, headers ] ] )
0178: * <li> Post ( file [, data [, headers ] ] )
0179: * <li> Post ( file [, stream [, headers ] ] )
0180: * <li> Put ( file , data [, headers ] )
0181: * <li> Put ( file , stream [, headers ] )
0182: * <li> Delete ( file [, headers ] )
0183: * <li> Options ( file [, headers [, data] ] )
0184: * <li> Options ( file [, headers [, stream] ] )
0185: * <li> Trace ( file [, headers ] )
0186: * </ul>
0187: *
0188: * @version 0.3-2 18/06/1999
0189: * @author Ronald Tschalär
0190: */
0191:
0192: public class HTTPConnection implements GlobalConstants,
0193: HTTPClientModuleConstants {
0194: /** The current version of this package. */
0195: public final static String version = "RPT-HTTPClient/0.3-2";
0196:
0197: /** The default context */
0198: private final static Object dflt_context = new Object();
0199:
0200: /** The current context */
0201: private Object Context = null;
0202:
0203: /** The protocol used on this connection */
0204: private int Protocol;
0205:
0206: /** The server's protocol version; M.m stored as (M<<16 | m) */
0207: int ServerProtocolVersion;
0208:
0209: /** Have we gotten the server's protocol version yet? */
0210: boolean ServProtVersKnown;
0211:
0212: /** The protocol version we send in a request; this is always HTTP/1.1
0213: unless we're talking to a broken server in which case it's HTTP/1.0 */
0214: private String RequestProtocolVersion;
0215:
0216: /** hack to force buffering of data instead of using chunked T-E */
0217: private static boolean no_chunked = false;
0218:
0219: /** hack to force HTTP/1.0 requests */
0220: private static boolean force_1_0 = false;
0221:
0222: /** The remote host this connection is associated with */
0223: private String Host;
0224:
0225: /** The remote port this connection is attached to */
0226: private int Port;
0227:
0228: /** The current proxy host to use (if any) */
0229: private String Proxy_Host = null;
0230:
0231: /** The current proxy port */
0232: private int Proxy_Port;
0233:
0234: /** The default proxy host to use (if any) */
0235: private static String Default_Proxy_Host = null;
0236:
0237: /** The default proxy port */
0238: private static int Default_Proxy_Port;
0239:
0240: /** The list of hosts for which no proxy is to be used */
0241: private static CIHashtable non_proxy_host_list = new CIHashtable();
0242: private static Vector non_proxy_dom_list = new Vector();
0243: private static Vector non_proxy_addr_list = new Vector();
0244: private static Vector non_proxy_mask_list = new Vector();
0245:
0246: /** The socks server to use */
0247: private SocksClient Socks_client = null;
0248:
0249: /** The default socks server to use */
0250: private static SocksClient Default_Socks_client = null;
0251:
0252: /** the current stream demultiplexor */
0253: private StreamDemultiplexor input_demux = null;
0254:
0255: /** a list of active stream demultiplexors */
0256: LinkedList DemuxList = new LinkedList();
0257:
0258: /** a list of active requests */
0259: private LinkedList RequestList = new LinkedList();
0260:
0261: /** does the server support keep-alive's? */
0262: private boolean DoesKeepAlive = false;
0263:
0264: /** have we been able to determine the above yet? */
0265: private boolean KeepAliveUnknown = true;
0266:
0267: /** the maximum number of requests over a HTTP/1.0 keep-alive connection */
0268: private int KeepAliveReqMax = -1;
0269:
0270: /** the number of requests over a HTTP/1.0 keep-alive connection left */
0271: private int KeepAliveReqLeft;
0272:
0273: /** hack to be able to disable pipelining */
0274: private static boolean NeverPipeline = false;
0275:
0276: /** hack to be able to disable keep-alives */
0277: private static boolean NoKeepAlives = false;
0278:
0279: /** hack to work around M$ bug */
0280: private static boolean haveMSLargeWritesBug = false;
0281:
0282: /** the default timeout to use for new connections */
0283: private static int DefaultTimeout = 0;
0284:
0285: /** the timeout to use for reading responses */
0286: private int Timeout;
0287:
0288: /** The list of default http headers */
0289: private NVPair[] DefaultHeaders = new NVPair[0];
0290:
0291: /** The default list of modules (as a Vector of Class objects) */
0292: private static Vector DefaultModuleList;
0293:
0294: /** The list of modules (as a Vector of Class objects) */
0295: private Vector ModuleList;
0296:
0297: /** controls whether modules are allowed to interact with user */
0298: private static boolean DefaultAllowUI = true;
0299:
0300: /** controls whether modules are allowed to interact with user */
0301: private boolean AllowUI;
0302:
0303: static {
0304: /*
0305: * Let's try and see if we can figure out whether any proxies are
0306: * being used.
0307: */
0308:
0309: try // JDK 1.1 naming
0310: {
0311: String host = System.getProperty("http.proxyHost");
0312: if (host == null)
0313: throw new Exception(); // try JDK 1.0.x naming
0314: int port = Integer.getInteger("http.proxyPort", -1)
0315: .intValue();
0316:
0317: if (DebugConn)
0318: System.err.println("Conn: using proxy " + host + ":"
0319: + port);
0320: setProxyServer(host, port);
0321: } catch (Exception e) {
0322: try // JDK 1.0.x naming
0323: {
0324: if (Boolean.getBoolean("proxySet")) {
0325: String host = System.getProperty("proxyHost");
0326: int port = Integer.getInteger("proxyPort", -1)
0327: .intValue();
0328: if (DebugConn)
0329: System.err.println("Conn: using proxy " + host
0330: + ":" + port);
0331: setProxyServer(host, port);
0332: }
0333: } catch (Exception ee) {
0334: Default_Proxy_Host = null;
0335: }
0336: }
0337:
0338: /*
0339: * now check for the non-proxy list
0340: */
0341: try {
0342: String hosts = System
0343: .getProperty("HTTPClient.nonProxyHosts");
0344: if (hosts == null)
0345: hosts = System.getProperty("http.nonProxyHosts");
0346:
0347: String[] list = Util.splitProperty(hosts);
0348: dontProxyFor(list);
0349: } catch (RuntimeException e) {
0350: }
0351:
0352: /*
0353: * we can't turn the JDK SOCKS handling off, so we don't use the
0354: * properties 'socksProxyHost' and 'socksProxyPort'. Instead we
0355: * define 'HTTPClient.socksHost', 'HTTPClient.socksPort' and
0356: * 'HTTPClient.socksVersion'.
0357: */
0358: try {
0359: String host = System.getProperty("HTTPClient.socksHost");
0360: if (host != null && host.length() > 0) {
0361: int port = Integer.getInteger("HTTPClient.socksPort",
0362: -1).intValue();
0363: int version = Integer.getInteger(
0364: "HTTPClient.socksVersion", -1).intValue();
0365: if (DebugConn)
0366: System.err.println("Conn: using SOCKS " + host
0367: + ":" + port);
0368: if (version == -1)
0369: setSocksServer(host, port);
0370: else
0371: setSocksServer(host, port, version);
0372: }
0373: } catch (Exception e) {
0374: Default_Socks_client = null;
0375: }
0376:
0377: // Set up module list
0378:
0379: String modules = "HTTPClient.RetryModule|"
0380: + "HTTPClient.CookieModule|"
0381: + "HTTPClient.RedirectionModule|"
0382: + "HTTPClient.AuthorizationModule|"
0383: + "HTTPClient.DefaultModule|"
0384: + "HTTPClient.TransferEncodingModule|"
0385: + "HTTPClient.ContentMD5Module|"
0386: + "HTTPClient.ContentEncodingModule";
0387:
0388: boolean in_applet = false;
0389: try {
0390: modules = System.getProperty("HTTPClient.Modules", modules);
0391: } catch (SecurityException se) {
0392: in_applet = true;
0393: }
0394:
0395: DefaultModuleList = new Vector();
0396: String[] list = Util.splitProperty(modules);
0397: for (int idx = 0; idx < list.length; idx++) {
0398: try {
0399: DefaultModuleList.addElement(Class.forName(list[idx]));
0400: if (DebugConn)
0401: System.err.println("Conn: added module "
0402: + list[idx]);
0403: } catch (ClassNotFoundException cnfe) {
0404: if (!in_applet)
0405: throw new NoClassDefFoundError(cnfe.getMessage());
0406:
0407: /* Just ignore it. This allows for example applets to just
0408: * load the necessary modules - if you don't need a module
0409: * then don't provide it, and it won't be added to the
0410: * list. The disadvantage is that if you accidently misstype
0411: * a module name this will lead to a "silent" error.
0412: */
0413: }
0414: }
0415:
0416: /*
0417: * Hack: disable pipelining
0418: */
0419: try {
0420: NeverPipeline = Boolean
0421: .getBoolean("HTTPClient.disable_pipelining");
0422: if (DebugConn)
0423: if (NeverPipeline)
0424: System.err.println("Conn: disabling pipelining");
0425: } catch (Exception e) {
0426: }
0427:
0428: /*
0429: * Hack: disable keep-alives
0430: */
0431: try {
0432: NoKeepAlives = Boolean
0433: .getBoolean("HTTPClient.disableKeepAlives");
0434: if (DebugConn)
0435: if (NoKeepAlives)
0436: System.err.println("Conn: disabling keep-alives");
0437: } catch (Exception e) {
0438: }
0439:
0440: /*
0441: * Hack: force HTTP/1.0 requests
0442: */
0443: try {
0444: force_1_0 = Boolean.getBoolean("HTTPClient.forceHTTP_1.0");
0445: if (DebugConn)
0446: if (force_1_0)
0447: System.err
0448: .println("Conn: forcing HTTP/1.0 requests");
0449: } catch (Exception e) {
0450: }
0451:
0452: /*
0453: * Hack: prevent chunking of request data
0454: */
0455: try {
0456: no_chunked = Boolean
0457: .getBoolean("HTTPClient.dontChunkRequests");
0458: if (DebugConn)
0459: if (no_chunked)
0460: System.err
0461: .println("Conn: never chunking requests");
0462: } catch (Exception e) {
0463: }
0464:
0465: /*
0466: * M$ bug: large writes hang the stuff
0467: */
0468: try {
0469: if (System.getProperty("os.name").indexOf("Windows") >= 0
0470: && System.getProperty("java.version").startsWith(
0471: "1.1"))
0472: haveMSLargeWritesBug = true;
0473: if (DebugConn)
0474: if (haveMSLargeWritesBug)
0475: System.err
0476: .println("Conn: splitting large writes into 20K chunks (M$ bug)");
0477: } catch (Exception e) {
0478: }
0479: }
0480:
0481: // Constructors
0482:
0483: /**
0484: * Constructs a connection to the host from where the applet was loaded.
0485: * Note that current security policies only let applets connect home.
0486: *
0487: * @param applet the current applet
0488: */
0489: public HTTPConnection(Applet applet)
0490: throws ProtocolNotSuppException {
0491: this (applet.getCodeBase().getProtocol(), applet.getCodeBase()
0492: .getHost(), applet.getCodeBase().getPort());
0493: }
0494:
0495: /**
0496: * Constructs a connection to the specified host on port 80
0497: *
0498: * @param host the host
0499: */
0500: public HTTPConnection(String host) {
0501: Setup(HTTP, host, 80);
0502: }
0503:
0504: /**
0505: * Constructs a connection to the specified host on the specified port
0506: *
0507: * @param host the host
0508: * @param port the port
0509: */
0510: public HTTPConnection(String host, int port) {
0511: Setup(HTTP, host, port);
0512: }
0513:
0514: /**
0515: * Constructs a connection to the specified host on the specified port,
0516: * using the specified protocol (currently only "http" is supported).
0517: *
0518: * @param prot the protocol
0519: * @param host the host
0520: * @param port the port, or -1 for the default port
0521: * @exception ProtocolNotSuppException if the protocol is not HTTP
0522: */
0523: public HTTPConnection(String prot, String host, int port)
0524: throws ProtocolNotSuppException {
0525: prot = prot.trim().toLowerCase();
0526:
0527: if (!prot.equals("http") && !prot.equals("https"))
0528: //if (!prot.equals("http"))
0529: throw new ProtocolNotSuppException("Unsupported protocol '"
0530: + prot + "'");
0531:
0532: if (prot.equals("http"))
0533: Setup(HTTP, host, port);
0534: else if (prot.equals("https"))
0535: Setup(HTTPS, host, port);
0536: else if (prot.equals("shttp"))
0537: Setup(SHTTP, host, port);
0538: else if (prot.equals("http-ng"))
0539: Setup(HTTP_NG, host, port);
0540: }
0541:
0542: /**
0543: * Constructs a connection to the host (port) as given in the url.
0544: *
0545: * @param url the url
0546: * @exception ProtocolNotSuppException if the protocol is not HTTP
0547: */
0548: public HTTPConnection(URL url) throws ProtocolNotSuppException {
0549: this (url.getProtocol(), url.getHost(), url.getPort());
0550: }
0551:
0552: /**
0553: * Sets the class variables. Must not be public.
0554: *
0555: * @param prot the protocol
0556: * @param host the host
0557: * @param port the port
0558: */
0559: private void Setup(int prot, String host, int port) {
0560: Protocol = prot;
0561: Host = host.trim().toLowerCase();
0562: Port = port;
0563:
0564: if (Port == -1)
0565: Port = URI.defaultPort(getProtocol());
0566:
0567: if (Default_Proxy_Host != null && !matchNonProxy(Host))
0568: setCurrentProxy(Default_Proxy_Host, Default_Proxy_Port);
0569: else
0570: setCurrentProxy(null, 0);
0571:
0572: Socks_client = Default_Socks_client;
0573: Timeout = DefaultTimeout;
0574: ModuleList = (Vector) DefaultModuleList.clone();
0575: AllowUI = DefaultAllowUI;
0576: if (NoKeepAlives)
0577: setDefaultHeaders(new NVPair[] { new NVPair("Connection",
0578: "close") });
0579: }
0580:
0581: /**
0582: * Determines if the given host matches any entry in the non-proxy list.
0583: *
0584: * @param host the host to match - must be trim()'d and lowercase
0585: * @return true if a match is found, false otherwise
0586: * @see #dontProxyFor(java.lang.String)
0587: */
0588: private boolean matchNonProxy(String host) {
0589: // Check host name list
0590:
0591: if (non_proxy_host_list.get(host) != null)
0592: return true;
0593:
0594: // Check domain name list
0595:
0596: for (int idx = 0; idx < non_proxy_dom_list.size(); idx++)
0597: if (host.endsWith((String) non_proxy_dom_list
0598: .elementAt(idx)))
0599: return true;
0600:
0601: // Check IP-address and subnet list
0602:
0603: if (non_proxy_addr_list.size() == 0)
0604: return false;
0605:
0606: InetAddress[] host_addr;
0607: try {
0608: host_addr = InetAddress.getAllByName(host);
0609: } catch (UnknownHostException uhe) {
0610: return false;
0611: } // maybe the proxy has better luck
0612:
0613: for (int idx = 0; idx < non_proxy_addr_list.size(); idx++) {
0614: byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
0615: byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
0616:
0617: ip_loop: for (int idx2 = 0; idx2 < host_addr.length; idx2++) {
0618: byte[] raw_addr = host_addr[idx2].getAddress();
0619: if (raw_addr.length != addr.length)
0620: continue;
0621:
0622: for (int idx3 = 0; idx3 < raw_addr.length; idx3++) {
0623: if ((raw_addr[idx3] & mask[idx3]) != (addr[idx3] & mask[idx3]))
0624: continue ip_loop;
0625: }
0626: return true;
0627: }
0628: }
0629:
0630: return false;
0631: }
0632:
0633: // Methods
0634:
0635: /**
0636: * Sends the HEAD request. This request is just like the corresponding
0637: * GET except that it only returns the headers and no data.
0638: *
0639: * @see #Get(java.lang.String)
0640: * @param file the absolute path of the file
0641: * @return an HTTPResponse structure containing the response
0642: * @exception java.io.IOException when an exception is returned from
0643: * the socket.
0644: * @exception ModuleException if an exception is encountered in any module.
0645: */
0646: public HTTPResponse Head(String file) throws IOException,
0647: ModuleException {
0648: return Head(file, (String) null, null);
0649: }
0650:
0651: /**
0652: * Sends the HEAD request. This request is just like the corresponding
0653: * GET except that it only returns the headers and no data.
0654: *
0655: * @see #Get(java.lang.String, HTTPClient.NVPair[])
0656: * @param file the absolute path of the file
0657: * @param form_data an array of Name/Value pairs
0658: * @return an HTTPResponse structure containing the response
0659: * @exception java.io.IOException when an exception is returned from
0660: * the socket.
0661: * @exception ModuleException if an exception is encountered in any module.
0662: */
0663: public HTTPResponse Head(String file, NVPair form_data[])
0664: throws IOException, ModuleException {
0665: return Head(file, form_data, null);
0666: }
0667:
0668: /**
0669: * Sends the HEAD request. This request is just like the corresponding
0670: * GET except that it only returns the headers and no data.
0671: *
0672: * @see #Get(java.lang.String, HTTPClient.NVPair[], HTTPClient.NVPair[])
0673: * @param file the absolute path of the file
0674: * @param form_data an array of Name/Value pairs
0675: * @param headers additional headers
0676: * @return an HTTPResponse structure containing the response
0677: * @exception java.io.IOException when an exception is returned from
0678: * the socket.
0679: * @exception ModuleException if an exception is encountered in any module.
0680: */
0681: public HTTPResponse Head(String file, NVPair[] form_data,
0682: NVPair[] headers) throws IOException, ModuleException {
0683: String File = stripRef(file), query = Codecs
0684: .nv2query(form_data);
0685: if (query != null && query.length() > 0)
0686: File += "?" + query;
0687:
0688: return setupRequest("HEAD", File, headers, null, null);
0689: }
0690:
0691: /**
0692: * Sends the HEAD request. This request is just like the corresponding
0693: * GET except that it only returns the headers and no data.
0694: *
0695: * @see #Get(java.lang.String, java.lang.String)
0696: * @param file the absolute path of the file
0697: * @param query the query string; it will be urlencoded
0698: * @return an HTTPResponse structure containing the response
0699: * @exception java.io.IOException when an exception is returned from
0700: * the socket.
0701: * @exception ModuleException if an exception is encountered in any module.
0702: */
0703: public HTTPResponse Head(String file, String query)
0704: throws IOException, ModuleException {
0705: return Head(file, query, null);
0706: }
0707:
0708: /**
0709: * Sends the HEAD request. This request is just like the corresponding
0710: * GET except that it only returns the headers and no data.
0711: *
0712: * @see #Get(java.lang.String, java.lang.String, HTTPClient.NVPair[])
0713: * @param file the absolute path of the file
0714: * @param query the query string; it will be urlencoded
0715: * @param headers additional headers
0716: * @return an HTTPResponse structure containing the response
0717: * @exception java.io.IOException when an exception is returned from
0718: * the socket.
0719: * @exception ModuleException if an exception is encountered in any module.
0720: */
0721: public HTTPResponse Head(String file, String query, NVPair[] headers)
0722: throws IOException, ModuleException {
0723: String File = stripRef(file);
0724: if (query != null && query.length() > 0)
0725: File += "?" + Codecs.URLEncode(query);
0726:
0727: return setupRequest("HEAD", File, headers, null, null);
0728: }
0729:
0730: /**
0731: * GETs the file.
0732: *
0733: * @param file the absolute path of the file
0734: * @return an HTTPResponse structure containing the response
0735: * @exception java.io.IOException when an exception is returned from
0736: * the socket.
0737: * @exception ModuleException if an exception is encountered in any module.
0738: */
0739: public HTTPResponse Get(String file) throws IOException,
0740: ModuleException {
0741: return Get(file, (String) null, null);
0742: }
0743:
0744: /**
0745: * GETs the file with a query consisting of the specified form-data.
0746: * The data is urlencoded, turned into a string of the form
0747: * "name1=value1&name2=value2" and then sent as a query string.
0748: *
0749: * @param file the absolute path of the file
0750: * @param form_data an array of Name/Value pairs
0751: * @return an HTTPResponse structure containing the response
0752: * @exception java.io.IOException when an exception is returned from
0753: * the socket.
0754: * @exception ModuleException if an exception is encountered in any module.
0755: */
0756: public HTTPResponse Get(String file, NVPair form_data[])
0757: throws IOException, ModuleException {
0758: return Get(file, form_data, null);
0759: }
0760:
0761: /**
0762: * GETs the file with a query consisting of the specified form-data.
0763: * The data is urlencoded, turned into a string of the form
0764: * "name1=value1&name2=value2" and then sent as a query string.
0765: *
0766: * @param file the absolute path of the file
0767: * @param form_data an array of Name/Value pairs
0768: * @param headers additional headers
0769: * @return an HTTPResponse structure containing the response
0770: * @exception java.io.IOException when an exception is returned from
0771: * the socket.
0772: * @exception ModuleException if an exception is encountered in any module.
0773: */
0774: public HTTPResponse Get(String file, NVPair[] form_data,
0775: NVPair[] headers) throws IOException, ModuleException {
0776: String File = stripRef(file), query = Codecs
0777: .nv2query(form_data);
0778: if (query != null && query.length() > 0)
0779: File += "?" + query;
0780:
0781: return setupRequest("GET", File, headers, null, null);
0782: }
0783:
0784: /**
0785: * GETs the file using the specified query string. The query string
0786: * is first urlencoded.
0787: *
0788: * @param file the absolute path of the file
0789: * @param query the query
0790: * @return an HTTPResponse structure containing the response
0791: * @exception java.io.IOException when an exception is returned from
0792: * the socket.
0793: * @exception ModuleException if an exception is encountered in any module.
0794: */
0795: public HTTPResponse Get(String file, String query)
0796: throws IOException, ModuleException {
0797: return Get(file, query, null);
0798: }
0799:
0800: /**
0801: * GETs the file using the specified query string. The query string
0802: * is first urlencoded.
0803: *
0804: * @param file the absolute path of the file
0805: * @param query the query string
0806: * @param headers additional headers
0807: * @return an HTTPResponse structure containing the response
0808: * @exception java.io.IOException when an exception is returned from
0809: * the socket.
0810: * @exception ModuleException if an exception is encountered in any module.
0811: */
0812: public HTTPResponse Get(String file, String query, NVPair[] headers)
0813: throws IOException, ModuleException {
0814: String File = stripRef(file);
0815: if (query != null && query.length() > 0)
0816: File += "?" + Codecs.URLEncode(query);
0817:
0818: return setupRequest("GET", File, headers, null, null);
0819: }
0820:
0821: /**
0822: * POSTs to the specified file. No data is sent.
0823: *
0824: * @param file the absolute path of the file
0825: * @return an HTTPResponse structure containing the response
0826: * @exception java.io.IOException when an exception is returned from
0827: * the socket.
0828: * @exception ModuleException if an exception is encountered in any module.
0829: */
0830: public HTTPResponse Post(String file) throws IOException,
0831: ModuleException {
0832: return Post(file, (byte[]) null, null);
0833: }
0834:
0835: /**
0836: * POSTs form-data to the specified file. The data is first urlencoded
0837: * and then turned into a string of the form "name1=value1&name2=value2".
0838: * A <var>Content-type</var> header with the value
0839: * <var>application/x-www-form-urlencoded</var> is added.
0840: *
0841: * @param file the absolute path of the file
0842: * @param form_data an array of Name/Value pairs
0843: * @return an HTTPResponse structure containing the response
0844: * @exception java.io.IOException when an exception is returned from
0845: * the socket.
0846: * @exception ModuleException if an exception is encountered in any module.
0847: */
0848: public HTTPResponse Post(String file, NVPair form_data[])
0849: throws IOException, ModuleException {
0850: NVPair[] headers = { new NVPair("Content-type",
0851: "application/x-www-form-urlencoded") };
0852:
0853: return Post(file, Codecs.nv2query(form_data), headers);
0854: }
0855:
0856: /**
0857: * POST's form-data to the specified file using the specified headers.
0858: * The data is first urlencoded and then turned into a string of the
0859: * form "name1=value1&name2=value2". If no <var>Content-type</var> header
0860: * is given then one is added with a value of
0861: * <var>application/x-www-form-urlencoded</var>.
0862: *
0863: * @param file the absolute path of the file
0864: * @param form_data an array of Name/Value pairs
0865: * @param headers additional headers
0866: * @return a HTTPResponse structure containing the response
0867: * @exception java.io.IOException when an exception is returned from
0868: * the socket.
0869: * @exception ModuleException if an exception is encountered in any module.
0870: */
0871: public HTTPResponse Post(String file, NVPair form_data[],
0872: NVPair headers[]) throws IOException, ModuleException {
0873: int idx;
0874: for (idx = 0; idx < headers.length; idx++)
0875: if (headers[idx].getName().equalsIgnoreCase("Content-type"))
0876: break;
0877: if (idx == headers.length) {
0878: headers = Util.resizeArray(headers, idx + 1);
0879: headers[idx] = new NVPair("Content-type",
0880: "application/x-www-form-urlencoded");
0881: }
0882:
0883: return Post(file, Codecs.nv2query(form_data), headers);
0884: }
0885:
0886: /**
0887: * POSTs the data to the specified file. The data is converted to an
0888: * array of bytes using the lower byte of each character.
0889: * The request is sent using the content-type "application/octet-stream".
0890: *
0891: * @see java.lang.String#getBytes(int, int, byte[], int)
0892: * @param file the absolute path of the file
0893: * @param data the data
0894: * @return an HTTPResponse structure containing the response
0895: * @exception java.io.IOException when an exception is returned from
0896: * the socket.
0897: * @exception ModuleException if an exception is encountered in any module.
0898: */
0899: public HTTPResponse Post(String file, String data)
0900: throws IOException, ModuleException {
0901: return Post(file, data, null);
0902: }
0903:
0904: /**
0905: * POSTs the data to the specified file using the specified headers.
0906: *
0907: * @param file the absolute path of the file
0908: * @param data the data
0909: * @param headers additional headers
0910: * @return an HTTPResponse structure containing the response
0911: * @exception java.io.IOException when an exception is returned from
0912: * the socket.
0913: * @exception ModuleException if an exception is encountered in any module.
0914: */
0915: public HTTPResponse Post(String file, String data, NVPair[] headers)
0916: throws IOException, ModuleException {
0917: byte tmp[] = null;
0918:
0919: if (data != null && data.length() > 0) {
0920: tmp = new byte[data.length()];
0921: data.getBytes(0, data.length(), tmp, 0);
0922: }
0923:
0924: return Post(file, tmp, headers);
0925: }
0926:
0927: /**
0928: * POSTs the raw data to the specified file.
0929: * The request is sent using the content-type "application/octet-stream"
0930: *
0931: * @param file the absolute path of the file
0932: * @param data the data
0933: * @return an HTTPResponse structure containing the response
0934: * @exception java.io.IOException when an exception is returned from
0935: * the socket.
0936: * @exception ModuleException if an exception is encountered in any module.
0937: */
0938: public HTTPResponse Post(String file, byte data[])
0939: throws IOException, ModuleException {
0940: return Post(file, data, null);
0941: }
0942:
0943: /**
0944: * POSTs the raw data to the specified file using the specified headers.
0945: *
0946: * @param file the absolute path of the file
0947: * @param data the data
0948: * @param headers additional headers
0949: * @return an HTTPResponse structure containing the response
0950: * @exception java.io.IOException when an exception is returned from
0951: * the socket.
0952: * @exception ModuleException if an exception is encountered in any module.
0953: */
0954: public HTTPResponse Post(String file, byte data[], NVPair[] headers)
0955: throws IOException, ModuleException {
0956: if (data == null)
0957: data = new byte[0]; // POST must always have a CL
0958: return setupRequest("POST", stripRef(file), headers, data, null);
0959: }
0960:
0961: /**
0962: * POSTs the data written to the output stream to the specified file.
0963: * The request is sent using the content-type "application/octet-stream"
0964: *
0965: * @param file the absolute path of the file
0966: * @param stream the output stream on which the data is written
0967: * @return an HTTPResponse structure containing the response
0968: * @exception java.io.IOException when an exception is returned from
0969: * the socket.
0970: * @exception ModuleException if an exception is encountered in any module.
0971: */
0972: public HTTPResponse Post(String file, HttpOutputStream stream)
0973: throws IOException, ModuleException {
0974: return Post(file, stream, null);
0975: }
0976:
0977: /**
0978: * POSTs the data written to the output stream to the specified file
0979: * using the specified headers.
0980: *
0981: * @param file the absolute path of the file
0982: * @param stream the output stream on which the data is written
0983: * @param headers additional headers
0984: * @return an HTTPResponse structure containing the response
0985: * @exception java.io.IOException when an exception is returned from
0986: * the socket.
0987: * @exception ModuleException if an exception is encountered in any module.
0988: */
0989: public HTTPResponse Post(String file, HttpOutputStream stream,
0990: NVPair[] headers) throws IOException, ModuleException {
0991: return setupRequest("POST", stripRef(file), headers, null,
0992: stream);
0993: }
0994:
0995: /**
0996: * PUTs the data into the specified file. The data is converted to an
0997: * array of bytes using the lower byte of each character.
0998: * The request ist sent using the content-type "application/octet-stream".
0999: *
1000: * @see java.lang.String#getBytes(int, int, byte[], int)
1001: * @param file the absolute path of the file
1002: * @param data the data
1003: * @return an HTTPResponse structure containing the response
1004: * @exception java.io.IOException when an exception is returned from
1005: * the socket.
1006: * @exception ModuleException if an exception is encountered in any module.
1007: */
1008: public HTTPResponse Put(String file, String data)
1009: throws IOException, ModuleException {
1010: return Put(file, data, null);
1011: }
1012:
1013: /**
1014: * PUTs the data into the specified file using the additional headers
1015: * for the request.
1016: *
1017: * @param file the absolute path of the file
1018: * @param data the data
1019: * @param headers additional headers
1020: * @return an HTTPResponse structure containing the response
1021: * @exception java.io.IOException when an exception is returned from
1022: * the socket.
1023: * @exception ModuleException if an exception is encountered in any module.
1024: */
1025: public HTTPResponse Put(String file, String data, NVPair[] headers)
1026: throws IOException, ModuleException {
1027: byte tmp[] = null;
1028:
1029: if (data != null) {
1030: tmp = new byte[data.length()];
1031: data.getBytes(0, data.length(), tmp, 0);
1032: }
1033:
1034: return Put(file, tmp, headers);
1035: }
1036:
1037: /**
1038: * PUTs the raw data into the specified file.
1039: * The request is sent using the content-type "application/octet-stream".
1040: *
1041: * @param file the absolute path of the file
1042: * @param data the data
1043: * @return an HTTPResponse structure containing the response
1044: * @exception java.io.IOException when an exception is returned from
1045: * the socket.
1046: * @exception ModuleException if an exception is encountered in any module.
1047: */
1048: public HTTPResponse Put(String file, byte data[])
1049: throws IOException, ModuleException {
1050: return Put(file, data, null);
1051: }
1052:
1053: /**
1054: * PUTs the raw data into the specified file using the additional
1055: * headers.
1056: *
1057: * @param file the absolute path of the file
1058: * @param data the data
1059: * @param headers any additional headers
1060: * @return an HTTPResponse structure containing the response
1061: * @exception java.io.IOException when an exception is returned from
1062: * the socket.
1063: * @exception ModuleException if an exception is encountered in any module.
1064: */
1065: public HTTPResponse Put(String file, byte data[], NVPair[] headers)
1066: throws IOException, ModuleException {
1067: if (data == null)
1068: data = new byte[0]; // PUT must always have a CL
1069: return setupRequest("PUT", stripRef(file), headers, data, null);
1070: }
1071:
1072: /**
1073: * PUTs the data written to the output stream into the specified file.
1074: * The request is sent using the content-type "application/octet-stream".
1075: *
1076: * @param file the absolute path of the file
1077: * @param stream the output stream on which the data is written
1078: * @return an HTTPResponse structure containing the response
1079: * @exception java.io.IOException when an exception is returned from
1080: * the socket.
1081: * @exception ModuleException if an exception is encountered in any module.
1082: */
1083: public HTTPResponse Put(String file, HttpOutputStream stream)
1084: throws IOException, ModuleException {
1085: return Put(file, stream, null);
1086: }
1087:
1088: /**
1089: * PUTs the data written to the output stream into the specified file
1090: * using the additional headers.
1091: *
1092: * @param file the absolute path of the file
1093: * @param stream the output stream on which the data is written
1094: * @param headers any additional headers
1095: * @return an HTTPResponse structure containing the response
1096: * @exception java.io.IOException when an exception is returned from
1097: * the socket.
1098: * @exception ModuleException if an exception is encountered in any module.
1099: */
1100: public HTTPResponse Put(String file, HttpOutputStream stream,
1101: NVPair[] headers) throws IOException, ModuleException {
1102: return setupRequest("PUT", stripRef(file), headers, null,
1103: stream);
1104: }
1105:
1106: /**
1107: * Request OPTIONS from the server. If <var>file</var> is "*" then
1108: * the request applies to the server as a whole; otherwise it applies
1109: * only to that resource.
1110: *
1111: * @param file the absolute path of the resource, or "*"
1112: * @return an HTTPResponse structure containing the response
1113: * @exception java.io.IOException when an exception is returned from
1114: * the socket.
1115: * @exception ModuleException if an exception is encountered in any module.
1116: */
1117: public HTTPResponse Options(String file) throws IOException,
1118: ModuleException {
1119: return Options(file, null, (byte[]) null);
1120: }
1121:
1122: /**
1123: * Request OPTIONS from the server. If <var>file</var> is "*" then
1124: * the request applies to the server as a whole; otherwise it applies
1125: * only to that resource.
1126: *
1127: * @param file the absolute path of the resource, or "*"
1128: * @param headers the headers containing optional info.
1129: * @return an HTTPResponse structure containing the response
1130: * @exception java.io.IOException when an exception is returned from
1131: * the socket.
1132: * @exception ModuleException if an exception is encountered in any module.
1133: */
1134: public HTTPResponse Options(String file, NVPair[] headers)
1135: throws IOException, ModuleException {
1136: return Options(file, headers, (byte[]) null);
1137: }
1138:
1139: /**
1140: * Request OPTIONS from the server. If <var>file</var> is "*" then
1141: * the request applies to the server as a whole; otherwise it applies
1142: * only to that resource.
1143: *
1144: * @param file the absolute path of the resource, or "*"
1145: * @param headers the headers containing optional info.
1146: * @param data any data to be sent in the optional body
1147: * @return an HTTPResponse structure containing the response
1148: * @exception java.io.IOException when an exception is returned from
1149: * the socket.
1150: * @exception ModuleException if an exception is encountered in any module.
1151: */
1152: public HTTPResponse Options(String file, NVPair[] headers,
1153: byte[] data) throws IOException, ModuleException {
1154: return setupRequest("OPTIONS", stripRef(file), headers, data,
1155: null);
1156: }
1157:
1158: /**
1159: * Request OPTIONS from the server. If <var>file</var> is "*" then
1160: * the request applies to the server as a whole; otherwise it applies
1161: * only to that resource.
1162: *
1163: * @param file the absolute path of the resource, or "*"
1164: * @param headers the headers containing optional info.
1165: * @param stream an output stream for sending the optional body
1166: * @return an HTTPResponse structure containing the response
1167: * @exception java.io.IOException when an exception is returned from
1168: * the socket.
1169: * @exception ModuleException if an exception is encountered in any module.
1170: */
1171: public HTTPResponse Options(String file, NVPair[] headers,
1172: HttpOutputStream stream) throws IOException,
1173: ModuleException {
1174: return setupRequest("OPTIONS", stripRef(file), headers, null,
1175: stream);
1176: }
1177:
1178: /**
1179: * Requests that <var>file</var> be DELETEd from the server.
1180: *
1181: * @param file the absolute path of the resource
1182: * @return an HTTPResponse structure containing the response
1183: * @exception java.io.IOException when an exception is returned from
1184: * the socket.
1185: * @exception ModuleException if an exception is encountered in any module.
1186: */
1187: public HTTPResponse Delete(String file) throws IOException,
1188: ModuleException {
1189: return Delete(file, null);
1190: }
1191:
1192: /**
1193: * Requests that <var>file</var> be DELETEd from the server.
1194: *
1195: * @param file the absolute path of the resource
1196: * @param headers additional headers
1197: * @return an HTTPResponse structure containing the response
1198: * @exception java.io.IOException when an exception is returned from
1199: * the socket.
1200: * @exception ModuleException if an exception is encountered in any module.
1201: */
1202: public HTTPResponse Delete(String file, NVPair[] headers)
1203: throws IOException, ModuleException {
1204: return setupRequest("DELETE", stripRef(file), headers, null,
1205: null);
1206: }
1207:
1208: /**
1209: * Requests a TRACE. Headers of particular interest here are "Via"
1210: * and "Max-Forwards".
1211: *
1212: * @param file the absolute path of the resource
1213: * @param headers additional headers
1214: * @return an HTTPResponse structure containing the response
1215: * @exception java.io.IOException when an exception is returned from
1216: * the socket.
1217: * @exception ModuleException if an exception is encountered in any module.
1218: */
1219: public HTTPResponse Trace(String file, NVPair[] headers)
1220: throws IOException, ModuleException {
1221: return setupRequest("TRACE", stripRef(file), headers, null,
1222: null);
1223: }
1224:
1225: /**
1226: * Requests a TRACE.
1227: *
1228: * @param file the absolute path of the resource
1229: * @return an HTTPResponse structure containing the response
1230: * @exception java.io.IOException when an exception is returned from
1231: * the socket.
1232: * @exception ModuleException if an exception is encountered in any module.
1233: */
1234: public HTTPResponse Trace(String file) throws IOException,
1235: ModuleException {
1236: return Trace(file, null);
1237: }
1238:
1239: /**
1240: * This is here to allow an arbitrary, non-standard request to be sent.
1241: * I'm assuming you know what you are doing...
1242: *
1243: * @param method the extension method
1244: * @param file the absolute path of the resource, or null
1245: * @param data optional data, or null
1246: * @param headers optional headers, or null
1247: * @return an HTTPResponse structure containing the response
1248: * @exception java.io.IOException when an exception is returned from
1249: * the socket.
1250: * @exception ModuleException if an exception is encountered in any module.
1251: */
1252: public HTTPResponse ExtensionMethod(String method, String file,
1253: byte[] data, NVPair[] headers) throws IOException,
1254: ModuleException {
1255: return setupRequest(method.trim(), stripRef(file), headers,
1256: data, null);
1257: }
1258:
1259: /**
1260: * This is here to allow an arbitrary, non-standard request to be sent.
1261: * I'm assuming you know what you are doing...
1262: *
1263: * @param method the extension method
1264: * @param file the absolute path of the resource, or null
1265: * @param stream optional output stream, or null
1266: * @param headers optional headers, or null
1267: * @return an HTTPResponse structure containing the response
1268: * @exception java.io.IOException when an exception is returned from
1269: * the socket.
1270: * @exception ModuleException if an exception is encountered in any module.
1271: */
1272: public HTTPResponse ExtensionMethod(String method, String file,
1273: HttpOutputStream os, NVPair[] headers) throws IOException,
1274: ModuleException {
1275: return setupRequest(method.trim(), stripRef(file), headers,
1276: null, os);
1277: }
1278:
1279: /**
1280: * Aborts all the requests currently in progress on this connection and
1281: * closes all associated sockets.
1282: *
1283: * <P>Note: there is a small window where a request method such as
1284: * <code>Get()</code> may have been invoked but the request has not
1285: * been built and added to the list. Any request in this window will
1286: * not be aborted.
1287: *
1288: * @since V0.2-3
1289: */
1290: public void stop() {
1291: for (Request req = (Request) RequestList.enumerate(); req != null; req = (Request) RequestList
1292: .next())
1293: req.aborted = true;
1294:
1295: for (StreamDemultiplexor demux = (StreamDemultiplexor) DemuxList
1296: .enumerate(); demux != null; demux = (StreamDemultiplexor) DemuxList
1297: .next())
1298: demux.abort();
1299: }
1300:
1301: /**
1302: * Sets the default http headers to be sent with each request. The
1303: * actual headers sent are determined as follows: for each header
1304: * specified in multiple places a value given as part of the request
1305: * takes priority over any default values set by this method, which
1306: * in turn takes priority over any built-in default values. A different
1307: * way of looking at it is that we start off with a list of all headers
1308: * specified with the request, then add any default headers set by this
1309: * method which aren't already in our list, and finally add any built-in
1310: * headers which aren't yet in the list. There are two exceptions to this
1311: * rule: "Content-length" and "Host" headers are always ignored; and when
1312: * posting form-data any default "Content-type" is ignored in favor of
1313: * the built-in "application/x-www-form-urlencoded" (however it will be
1314: * overriden by any content-type header specified as part of the request).
1315: *
1316: * <P>Typical headers you might want to set here are "Accept" and its
1317: * "Accept-*" relatives, "Connection", "From", "User-Agent", etc.
1318: *
1319: * @param headers an array of header-name/value pairs (do not give the
1320: * separating ':').
1321: */
1322: public void setDefaultHeaders(NVPair[] headers) {
1323: int length = (headers == null ? 0 : headers.length);
1324: NVPair[] def_hdrs = new NVPair[length];
1325:
1326: // weed out undesired headers
1327: int sidx, didx;
1328: for (sidx = 0, didx = 0; sidx < length; sidx++) {
1329: String name = headers[sidx].getName().trim();
1330: if (name.equalsIgnoreCase("Content-length")
1331: || name.equalsIgnoreCase("Host"))
1332: continue;
1333:
1334: def_hdrs[didx++] = headers[sidx];
1335: }
1336:
1337: if (didx < length)
1338: def_hdrs = Util.resizeArray(DefaultHeaders, didx);
1339:
1340: synchronized (DefaultHeaders) {
1341: DefaultHeaders = def_hdrs;
1342: }
1343: }
1344:
1345: /**
1346: * Gets the current list of default http headers.
1347: *
1348: * @return an array of header/value pairs.
1349: */
1350: public NVPair[] getDefaultHeaders() {
1351: //return (NVPair[]) DefaultHeaders.clone(); JDK 1.1 Only
1352:
1353: synchronized (DefaultHeaders) {
1354: NVPair[] headers = new NVPair[DefaultHeaders.length];
1355: System.arraycopy(DefaultHeaders, 0, headers, 0,
1356: headers.length);
1357: return headers;
1358: }
1359: }
1360:
1361: /**
1362: * Returns the protocol this connection is talking.
1363: *
1364: * @return a string containing the (lowercased) protocol
1365: */
1366: public String getProtocol() {
1367: switch (Protocol) {
1368: case HTTP:
1369: return "http";
1370: case HTTPS:
1371: return "https";
1372: case SHTTP:
1373: return "shttp";
1374: case HTTP_NG:
1375: return "http-ng";
1376: default:
1377: throw new Error(
1378: "HTTPClient Internal Error: invalid protocol "
1379: + Protocol);
1380: }
1381: }
1382:
1383: /**
1384: * Returns the host this connection is talking to.
1385: *
1386: * @return a string containing the (lowercased) host name.
1387: */
1388: public String getHost() {
1389: return Host;
1390: }
1391:
1392: /**
1393: * Returns the port this connection connects to. This is always the
1394: * actual port number, never -1.
1395: *
1396: * @return the port number
1397: */
1398: public int getPort() {
1399: return Port;
1400: }
1401:
1402: /**
1403: * Returns the host of the proxy this connection is using.
1404: *
1405: * @return a string containing the (lowercased) host name.
1406: */
1407: public String getProxyHost() {
1408: return Proxy_Host;
1409: }
1410:
1411: /**
1412: * Returns the port of the proxy this connection is using.
1413: *
1414: * @return the port number
1415: */
1416: public int getProxyPort() {
1417: return Proxy_Port;
1418: }
1419:
1420: /**
1421: * See if the given uri is compatible with this connection. Compatible
1422: * means that the given uri can be retrieved using this connection
1423: * object.
1424: *
1425: * @param uri the URI to check
1426: * @return true if they're compatible, false otherwise
1427: * @since V0.3-2
1428: */
1429: public boolean isCompatibleWith(URI uri) {
1430: if (!uri.getScheme().equals(getProtocol())
1431: || !uri.getHost().equalsIgnoreCase(Host))
1432: return false;
1433:
1434: int port = uri.getPort();
1435: if (port == -1)
1436: port = URI.defaultPort(uri.getScheme());
1437: return port == Port;
1438: }
1439:
1440: /**
1441: * Sets/Resets raw mode. In raw mode all modules are bypassed, meaning
1442: * the automatic handling of authorization requests, redirections,
1443: * cookies, etc. is turned off.
1444: *
1445: * <P>The default is false.
1446: *
1447: * @deprecated This is not really needed anymore; in V0.2 request were
1448: * synchronous and therefore to do pipelining you needed
1449: * to disable the processing of responses.
1450: * @see #removeModule(java.lang.Class)
1451: *
1452: * @param raw if true removes all modules (except for the retry module)
1453: */
1454: public void setRawMode(boolean raw) {
1455: // Don't remove the retry module
1456: String[] modules = { "HTTPClient.CookieModule",
1457: "HTTPClient.RedirectionModule",
1458: "HTTPClient.AuthorizationModule",
1459: "HTTPClient.DefaultModule",
1460: "HTTPClient.TransferEncodingModule",
1461: "HTTPClient.ContentMD5Module",
1462: "HTTPClient.ContentEncodingModule" };
1463:
1464: for (int idx = 0; idx < modules.length; idx++) {
1465: try {
1466: if (raw)
1467: removeModule(Class.forName(modules[idx]));
1468: else
1469: addModule(Class.forName(modules[idx]), -1);
1470: } catch (ClassNotFoundException cnfe) {
1471: }
1472: }
1473: }
1474:
1475: /**
1476: * Sets the default timeout value to be used for each new HTTPConnection.
1477: * The default is 0.
1478: *
1479: * @param time the timeout in milliseconds.
1480: * @see #setTimeout(int)
1481: */
1482: public static void setDefaultTimeout(int time) {
1483: DefaultTimeout = time;
1484: }
1485:
1486: /**
1487: * Gets the default timeout value to be used for each new HTTPConnection.
1488: *
1489: * @return the timeout in milliseconds.
1490: * @see #setTimeout(int)
1491: */
1492: public static int getDefaultTimeout() {
1493: return DefaultTimeout;
1494: }
1495:
1496: /**
1497: * Sets the timeout to be used for creating connections and reading
1498: * responses. When a timeout expires the operation will throw an
1499: * InterruptedIOException. The operation may be restarted again
1500: * afterwards. If the operation is not restarted and it is a read
1501: * operation (i.e HTTPResponse.xxxx()) then <code>stop()</code>
1502: * <strong>should</strong> be invoked.
1503: *
1504: * <P>When creating new sockets the timeout will limit the time spent
1505: * doing the host name translation and establishing the connection with
1506: * the server.
1507: *
1508: * <P>The timeout also influences the reading of the response headers.
1509: * However, it does not specify a how long, for example, getStatusCode()
1510: * may take, as might be assumed. Instead it specifies how long a read
1511: * on the socket may take. If the response dribbles in slowly with
1512: * packets arriving quicker than the timeout then the method will
1513: * complete normally. I.e. the exception is only thrown if nothing
1514: * arrives on the socket for the specified time. Furthermore, the
1515: * timeout only influences the reading of the headers, not the reading
1516: * of the body.
1517: *
1518: * <P>Read Timeouts are associated with responses, so that you may change
1519: * this value before each request and it won't affect the reading of
1520: * responses to previous requests.
1521: *
1522: * <P><em>Note:</em> The read timeout only works with JDK 1.1 or later.
1523: * Using this method with JDK 1.0.2 or earlier will only influence the
1524: * connection creation.
1525: *
1526: * @param time the time in milliseconds. A time of 0 means wait
1527: * indefinitely.
1528: * @see #stop()
1529: */
1530: public void setTimeout(int time) {
1531: Timeout = time;
1532: }
1533:
1534: /**
1535: * Gets the timeout used for reading response data.
1536: *
1537: * @return the current timeout value
1538: * @see #setTimeout(int)
1539: */
1540: public int getTimeout() {
1541: return Timeout;
1542: }
1543:
1544: /**
1545: * Controls whether modules are allowed to prompt the user or pop up
1546: * dialogs if neccessary.
1547: *
1548: * @param allow if true allows modules to interact with user.
1549: */
1550: public void setAllowUserInteraction(boolean allow) {
1551: AllowUI = allow;
1552: }
1553:
1554: /**
1555: * returns whether modules are allowed to prompt or popup dialogs
1556: * if neccessary.
1557: *
1558: * @return true if modules are allowed to interact with user.
1559: */
1560: public boolean getAllowUserInteraction() {
1561: return AllowUI;
1562: }
1563:
1564: /**
1565: * Sets the default allow-user-action.
1566: *
1567: * @param allow if true allows modules to interact with user.
1568: */
1569: public static void setDefaultAllowUserInteraction(boolean allow) {
1570: DefaultAllowUI = allow;
1571: }
1572:
1573: /**
1574: * Gets the default allow-user-action.
1575: *
1576: * @return true if modules are allowed to interact with user.
1577: */
1578: public static boolean getDefaultAllowUserInteraction() {
1579: return DefaultAllowUI;
1580: }
1581:
1582: /**
1583: * Returns the default list of modules.
1584: *
1585: * @return an array of classes
1586: */
1587: public static Class[] getDefaultModules() {
1588: synchronized (DefaultModuleList) {
1589: Class[] modules = new Class[DefaultModuleList.size()];
1590: DefaultModuleList.copyInto(modules);
1591: return modules;
1592: }
1593: }
1594:
1595: /**
1596: * Adds a module to the default list. It must implement the
1597: * <var>HTTPClientModule</var> interface. If the module is already in
1598: * the list then this method does nothing.
1599: *
1600: * <P>Example:
1601: * <PRE>
1602: * HTTPConnection.addDefaultModule(Class.forName("HTTPClient.CookieModule"), 1);
1603: * </PRE>
1604: * adds the cookie module as the second module in the list.
1605: *
1606: * <P>The default list is created at class initialization time from the
1607: * property <var>HTTPClient.Modules</var>. This must contain a "|"
1608: * separated list of classes in the order they're to be invoked. If this
1609: * property is not set it defaults to:
1610: *
1611: * "HTTPClient.RetryModule | HTTPClient.CookieModule |
1612: * HTTPClient.RedirectionModule | HTTPClient.AuthorizationModule |
1613: * HTTPClient.DefaultModule | HTTPClient.TransferEncodingModule |
1614: * HTTPClient.ContentMD5Module | HTTPClient.ContentEncodingModule"
1615: *
1616: * @see HTTPClientModule
1617: * @param module the module's Class object
1618: * @param pos the position of this module in the list; if <var>pos</var>
1619: * >= 0 then this is the absolute position in the list (0 is
1620: * the first position); if <var>pos</var> < 0 then this is
1621: * the position relative to the end of the list (-1 means
1622: * the last element, -2 the second to last element, etc).
1623: * @return true if module was successfully added; false if the
1624: * module is already in the list.
1625: * @exception ArrayIndexOutOfBoundsException if <var>pos</var> >
1626: * list-size or if <var>pos</var> < -(list-size).
1627: * @exception ClassCastException if <var>module</var> does not
1628: * implement the <var>HTTPClientModule</var> interface.
1629: * @exception RuntimeException if <var>module</var> cannot be
1630: * instantiated.
1631: */
1632: public static boolean addDefaultModule(Class module, int pos) {
1633: // check if module implements HTTPClientModule
1634: try {
1635: HTTPClientModule tmp = (HTTPClientModule) module
1636: .newInstance();
1637: } catch (RuntimeException re) {
1638: throw re;
1639: } catch (Exception e) {
1640: throw new RuntimeException(e.toString());
1641: }
1642:
1643: synchronized (DefaultModuleList) {
1644: // check if module already in list
1645: if (DefaultModuleList.contains(module))
1646: return false;
1647:
1648: // add module to list
1649: if (pos < 0)
1650: DefaultModuleList.insertElementAt(module,
1651: DefaultModuleList.size() + pos + 1);
1652: else
1653: DefaultModuleList.insertElementAt(module, pos);
1654: }
1655:
1656: if (DebugConn)
1657: System.err.println("Conn: Added module "
1658: + module.getName() + " to default list");
1659:
1660: return true;
1661: }
1662:
1663: /**
1664: * Removes a module from the default list. If the module is not in the
1665: * list it does nothing.
1666: *
1667: * @param module the module's Class object
1668: * @return true if module was successfully removed; false otherwise
1669: */
1670: public static boolean removeDefaultModule(Class module) {
1671: boolean removed = DefaultModuleList.removeElement(module);
1672:
1673: if (DebugConn)
1674: if (removed)
1675: System.err.println("Conn: Removed module "
1676: + module.getName() + " from default list");
1677:
1678: return removed;
1679: }
1680:
1681: /**
1682: * Returns the list of modules used currently.
1683: *
1684: * @return an array of classes
1685: */
1686: public Class[] getModules() {
1687: synchronized (ModuleList) {
1688: Class[] modules = new Class[ModuleList.size()];
1689: ModuleList.copyInto(modules);
1690: return modules;
1691: }
1692: }
1693:
1694: /**
1695: * Adds a module to the current list. It must implement the
1696: * <var>HTTPClientModule</var> interface. If the module is already in
1697: * the list then this method does nothing.
1698: *
1699: * @see HTTPClientModule
1700: * @param module the module's Class object
1701: * @param pos the position of this module in the list; if <var>pos</var>
1702: * >= 0 then this is the absolute position in the list (0 is
1703: * the first position); if <var>pos</var> < 0 then this is
1704: * the position relative to the end of the list (-1 means
1705: * the last element, -2 the second to last element, etc).
1706: * @return true if module was successfully added; false if the
1707: * module is already in the list.
1708: * @exception ArrayIndexOutOfBoundsException if <var>pos</var> >
1709: * list-size or if <var>pos</var> < -(list-size).
1710: * @exception ClassCastException if <var>module</var> does not
1711: * implement the <var>HTTPClientModule</var> interface.
1712: * @exception RuntimeException if <var>module</var> cannot be
1713: * instantiated.
1714: */
1715: public boolean addModule(Class module, int pos) {
1716: // check if module implements HTTPClientModule
1717: try {
1718: HTTPClientModule tmp = (HTTPClientModule) module
1719: .newInstance();
1720: } catch (RuntimeException re) {
1721: throw re;
1722: } catch (Exception e) {
1723: throw new RuntimeException(e.toString());
1724: }
1725:
1726: synchronized (ModuleList) {
1727: // check if module already in list
1728: if (ModuleList.contains(module))
1729: return false;
1730:
1731: // add module to list
1732: if (pos < 0)
1733: ModuleList.insertElementAt(module, ModuleList.size()
1734: + pos + 1);
1735: else
1736: ModuleList.insertElementAt(module, pos);
1737: }
1738:
1739: return true;
1740: }
1741:
1742: /**
1743: * Removes a module from the current list. If the module is not in the
1744: * list it does nothing.
1745: *
1746: * @param module the module's Class object
1747: * @return true if module was successfully removed; false otherwise
1748: */
1749: public boolean removeModule(Class module) {
1750: if (module == null)
1751: return false;
1752: return ModuleList.removeElement(module);
1753: }
1754:
1755: /**
1756: * Sets the current context. The context is used by modules such as
1757: * the AuthorizationModule and the CookieModule which keep lists of
1758: * info that is normally shared between all instances of HTTPConnection.
1759: * This is usually the desired behaviour. However, in some cases one
1760: * would like to simulate multiple independent clients within the
1761: * same application and hence the sharing of such info should be
1762: * restricted. This is where the context comes in. Modules will only
1763: * share their info between requests using the same context (i.e. they
1764: * keep multiple lists, one for each context).
1765: *
1766: * <P>The context may be any object. Contexts are considered equal
1767: * if <code>equals()</code> returns true. Examples of useful context
1768: * objects are threads (e.g. if you are running multiple clients, one
1769: * per thread) and sockets (e.g. if you are implementing a gateway).
1770: *
1771: * <P>When a new HTTPConnection is created it is initialized with a
1772: * default context which is the same for all instances. This method
1773: * must be invoked immediately after a new HTTPConnection is created
1774: * and before any request method is invoked. Furthermore, this method
1775: * may only be called once (i.e. the context is "sticky").
1776: *
1777: * @param context the new context; must be non-null
1778: * @exception IllegalArgumentException if <var>context</var> is null
1779: * @exception RuntimeException if the context has already been set
1780: */
1781: public void setContext(Object context) {
1782: if (context == null)
1783: throw new IllegalArgumentException(
1784: "Context must be non-null");
1785: if (Context != null)
1786: throw new RuntimeException("Context already set");
1787:
1788: Context = context;
1789: }
1790:
1791: /**
1792: * Returns the current context.
1793: *
1794: * @see #setContext(java.lang.Object)
1795: * @return the current context, or the default context if
1796: * <code>setContext()</code> hasn't been invoked
1797: */
1798: public Object getContext() {
1799: if (Context != null)
1800: return Context;
1801: else
1802: return dflt_context;
1803: }
1804:
1805: /**
1806: * Returns the default context.
1807: *
1808: * @see #setContext(java.lang.Object)
1809: * @return the default context
1810: */
1811: static Object getDefaultContext() {
1812: return dflt_context;
1813: }
1814:
1815: /**
1816: * Adds an authorization entry for the "digest" authorization scheme to
1817: * the list. If an entry already exists for the "digest" scheme and the
1818: * specified realm then it is overwritten.
1819: *
1820: * <P>This is a convenience method and just invokes the corresponding
1821: * method in AuthorizationInfo.
1822: *
1823: * @param realm the realm
1824: * @param user the username
1825: * @param passw the password
1826: * @see AuthorizationInfo#addDigestAuthorization(java.lang.String, int, java.lang.String, java.lang.String, java.lang.String)
1827: */
1828: public void addDigestAuthorization(String realm, String user,
1829: String passwd) {
1830: AuthorizationInfo.addDigestAuthorization(Host, Port, realm,
1831: user, passwd, getContext());
1832: }
1833:
1834: /**
1835: * Adds an authorization entry for the "basic" authorization scheme to
1836: * the list. If an entry already exists for the "basic" scheme and the
1837: * specified realm then it is overwritten.
1838: *
1839: * <P>This is a convenience method and just invokes the corresponding
1840: * method in AuthorizationInfo.
1841: *
1842: * @param realm the realm
1843: * @param user the username
1844: * @param passw the password
1845: * @see AuthorizationInfo#addBasicAuthorization(java.lang.String, int, java.lang.String, java.lang.String, java.lang.String)
1846: */
1847: public void addBasicAuthorization(String realm, String user,
1848: String passwd) {
1849: AuthorizationInfo.addBasicAuthorization(Host, Port, realm,
1850: user, passwd, getContext());
1851: }
1852:
1853: /**
1854: * Sets the default proxy server to use. The proxy will only be used
1855: * for new <var>HTTPConnection</var>s created after this call and will
1856: * not affect currrent instances of <var>HTTPConnection</var>. A null
1857: * or empty string <var>host</var> parameter disables the proxy.
1858: *
1859: * <P>In an application or using the Appletviewer an alternative to
1860: * this method is to set the following properties (either in the
1861: * properties file or on the command line):
1862: * <var>http.proxyHost</var> and <var>http.proxyPort</var>. Whether
1863: * <var>http.proxyHost</var> is set or not determines whether a proxy
1864: * server is used.
1865: *
1866: * <P>If the proxy server requires authorization and you wish to set
1867: * this authorization information in the code, then you may use any
1868: * of the <var>AuthorizationInfo.addXXXAuthorization()</var> methods to
1869: * do so. Specify the same <var>host</var> and <var>port</var> as in
1870: * this method. If you have not given any authorization info and the
1871: * proxy server requires authorization then you will be prompted for
1872: * the necessary info via a popup the first time you do a request.
1873: *
1874: * @see #setCurrentProxy(java.lang.String,int)
1875: * @param host the host on which the proxy server resides.
1876: * @param port the port the proxy server is listening on.
1877: */
1878: public static void setProxyServer(String host, int port) {
1879: if (host == null || host.trim().length() == 0)
1880: Default_Proxy_Host = null;
1881: else {
1882: Default_Proxy_Host = host.trim().toLowerCase();
1883: Default_Proxy_Port = port;
1884: }
1885: }
1886:
1887: /**
1888: * Sets the proxy used by this instance. This can be used to override
1889: * the proxy setting inherited from the default proxy setting. A null
1890: * or empty string <var>host</var> parameter disables the proxy.
1891: *
1892: * <P>Note that if you set a proxy for the connection using this
1893: * method, and a request made over this connection is redirected
1894: * to a different server, then the connection used for new server
1895: * will <em>not</em> pick this proxy setting, but instead will use
1896: * the default proxy settings.
1897: *
1898: * @see #setProxyServer(java.lang.String,int)
1899: * @param host the host the proxy runs on
1900: * @param port the port the proxy is listening on
1901: */
1902: public synchronized void setCurrentProxy(String host, int port) {
1903: if (host == null || host.trim().length() == 0)
1904: Proxy_Host = null;
1905: else {
1906: Proxy_Host = host.trim().toLowerCase();
1907: if (port <= 0)
1908: Proxy_Port = 80;
1909: else
1910: Proxy_Port = port;
1911: }
1912:
1913: // the proxy might be talking a different version, so renegotiate
1914: switch (Protocol) {
1915: case HTTP:
1916: case HTTPS:
1917: if (force_1_0) {
1918: ServerProtocolVersion = HTTP_1_0;
1919: ServProtVersKnown = true;
1920: RequestProtocolVersion = "HTTP/1.0";
1921: } else {
1922: ServerProtocolVersion = HTTP_1_1;
1923: ServProtVersKnown = false;
1924: RequestProtocolVersion = "HTTP/1.1";
1925: }
1926: break;
1927: case HTTP_NG:
1928: ServerProtocolVersion = -1; /* Unknown */
1929: ServProtVersKnown = false;
1930: RequestProtocolVersion = "";
1931: break;
1932: case SHTTP:
1933: ServerProtocolVersion = -1; /* Unknown */
1934: ServProtVersKnown = false;
1935: RequestProtocolVersion = "Secure-HTTP/1.3";
1936: break;
1937: default:
1938: throw new Error(
1939: "HTTPClient Internal Error: invalid protocol "
1940: + Protocol);
1941: }
1942:
1943: KeepAliveUnknown = true;
1944: DoesKeepAlive = false;
1945:
1946: input_demux = null;
1947: early_stall = null;
1948: late_stall = null;
1949: prev_resp = null;
1950: }
1951:
1952: /**
1953: * Add <var>host</var> to the list of hosts which should be accessed
1954: * directly, not via any proxy set by <code>setProxyServer()</code>.
1955: *
1956: * <P>The <var>host</var> may be any of:
1957: * <UL>
1958: * <LI>a complete host name (e.g. "www.disney.com")
1959: * <LI>a domain name; domain names must begin with a dot (e.g.
1960: * ".disney.com")
1961: * <LI>an IP-address (e.g. "12.34.56.78")
1962: * <LI>an IP-subnet, specified as an IP-address and a netmask separated
1963: * by a "/" (e.g. "34.56.78/255.255.255.192"); a 0 bit in the netmask
1964: * means that that bit won't be used in the comparison (i.e. the
1965: * addresses are AND'ed with the netmask before comparison).
1966: * </UL>
1967: *
1968: * <P>The two properties <var>HTTPClient.nonProxyHosts</var> and
1969: * <var>http.nonProxyHosts</var> are used when this class is loaded to
1970: * initialize the list of non-proxy hosts. The second property is only
1971: * read if the first one is not set; the second property is also used
1972: * the JDK's URLConnection. These properties must contain a "|"
1973: * separated list of entries which conform to the above rules for the
1974: * <var>host</var> parameter (e.g. "11.22.33.44|.disney.com").
1975: *
1976: * @param host a host name, domain name, IP-address or IP-subnet.
1977: * @exception ParseException if the length of the netmask does not match
1978: * the length of the IP-address
1979: */
1980: public static void dontProxyFor(String host) throws ParseException {
1981: host = host.trim().toLowerCase();
1982:
1983: // check for domain name
1984:
1985: if (host.charAt(0) == '.') {
1986: if (!non_proxy_dom_list.contains(host))
1987: non_proxy_dom_list.addElement(host);
1988: return;
1989: }
1990:
1991: // check for host name
1992:
1993: for (int idx = 0; idx < host.length(); idx++) {
1994: if (!Character.isDigit(host.charAt(idx))
1995: && host.charAt(idx) != '.'
1996: && host.charAt(idx) != '/') {
1997: non_proxy_host_list.put(host, "");
1998: return;
1999: }
2000: }
2001:
2002: // must be an IP-address
2003:
2004: byte[] ip_addr;
2005: byte[] ip_mask;
2006: int slash;
2007: if ((slash = host.indexOf('/')) != -1) // IP subnet
2008: {
2009: ip_addr = string2arr(host.substring(0, slash));
2010: ip_mask = string2arr(host.substring(slash + 1));
2011: if (ip_addr.length != ip_mask.length)
2012: throw new ParseException("length of IP-address ("
2013: + ip_addr.length + ") != length of netmask ("
2014: + ip_mask.length + ")");
2015: } else {
2016: ip_addr = string2arr(host);
2017: ip_mask = new byte[ip_addr.length];
2018: for (int idx = 0; idx < ip_mask.length; idx++)
2019: ip_mask[idx] = (byte) 255;
2020: }
2021:
2022: // check if addr or subnet already exists
2023:
2024: ip_loop: for (int idx = 0; idx < non_proxy_addr_list.size(); idx++) {
2025: byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
2026: byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
2027: if (addr.length != ip_addr.length)
2028: continue;
2029:
2030: for (int idx2 = 0; idx2 < addr.length; idx2++) {
2031: if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2])
2032: || (mask[idx2] != ip_mask[idx2]))
2033: continue ip_loop;
2034: }
2035:
2036: return; // already exists
2037: }
2038: non_proxy_addr_list.addElement(ip_addr);
2039: non_proxy_mask_list.addElement(ip_mask);
2040: }
2041:
2042: /**
2043: * Convenience method to add a number of hosts at once. If any one
2044: * host is null or cannot be parsed it is ignored.
2045: *
2046: * @param hosts The list of hosts to set
2047: * @see #dontProxyFor(java.lang.String)
2048: * @since V0.3-2
2049: */
2050: public static void dontProxyFor(String[] hosts) {
2051: if (hosts == null || hosts.length == 0)
2052: return;
2053:
2054: for (int idx = 0; idx < hosts.length; idx++) {
2055: try {
2056: if (hosts[idx] != null)
2057: dontProxyFor(hosts[idx]);
2058: } catch (ParseException pe) {
2059: // ignore it
2060: }
2061: }
2062: }
2063:
2064: /**
2065: * Remove <var>host</var> from the list of hosts for which the proxy
2066: * should not be used. The syntax for <var>host</var> is specified in
2067: * <code>dontProxyFor()</code>.
2068: *
2069: * @param host a host name, domain name, IP-address or IP-subnet.
2070: * @return true if the remove was sucessful, false otherwise
2071: * @exception ParseException if the length of the netmask does not match
2072: * the length of the IP-address
2073: * @see #dontProxyFor(java.lang.String)
2074: */
2075: public static boolean doProxyFor(String host) throws ParseException {
2076: host = host.trim().toLowerCase();
2077:
2078: // check for domain name
2079:
2080: if (host.charAt(0) == '.')
2081: return non_proxy_dom_list.removeElement(host);
2082:
2083: // check for host name
2084:
2085: for (int idx = 0; idx < host.length(); idx++) {
2086: if (!Character.isDigit(host.charAt(idx))
2087: && host.charAt(idx) != '.'
2088: && host.charAt(idx) != '/')
2089: return (non_proxy_host_list.remove(host) != null);
2090: }
2091:
2092: // must be an IP-address
2093:
2094: byte[] ip_addr;
2095: byte[] ip_mask;
2096: int slash;
2097: if ((slash = host.indexOf('/')) != -1) // IP subnet
2098: {
2099: ip_addr = string2arr(host.substring(0, slash));
2100: ip_mask = string2arr(host.substring(slash + 1));
2101: if (ip_addr.length != ip_mask.length)
2102: throw new ParseException("length of IP-address ("
2103: + ip_addr.length + ") != length of netmask ("
2104: + ip_mask.length + ")");
2105: } else {
2106: ip_addr = string2arr(host);
2107: ip_mask = new byte[ip_addr.length];
2108: for (int idx = 0; idx < ip_mask.length; idx++)
2109: ip_mask[idx] = (byte) 255;
2110: }
2111:
2112: ip_loop: for (int idx = 0; idx < non_proxy_addr_list.size(); idx++) {
2113: byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
2114: byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
2115: if (addr.length != ip_addr.length)
2116: continue;
2117:
2118: for (int idx2 = 0; idx2 < addr.length; idx2++) {
2119: if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2])
2120: || (mask[idx2] != ip_mask[idx2]))
2121: continue ip_loop;
2122: }
2123:
2124: non_proxy_addr_list.removeElementAt(idx);
2125: non_proxy_mask_list.removeElementAt(idx);
2126: return true;
2127: }
2128: return false;
2129: }
2130:
2131: /**
2132: * Turn an IP-address string into an array (e.g. "12.34.56.78" into
2133: * { 12, 34, 56, 78 }).
2134: *
2135: * @param ip IP-address
2136: * @return IP-address in network byte order
2137: */
2138: private static byte[] string2arr(String ip) {
2139: byte[] arr;
2140: char[] ip_char = new char[ip.length()];
2141: ip.getChars(0, ip_char.length, ip_char, 0);
2142:
2143: int cnt = 0;
2144: for (int idx = 0; idx < ip_char.length; idx++)
2145: if (ip_char[idx] == '.')
2146: cnt++;
2147: arr = new byte[cnt + 1];
2148:
2149: cnt = 0;
2150: int pos = 0;
2151: for (int idx = 0; idx < ip_char.length; idx++)
2152: if (ip_char[idx] == '.') {
2153: arr[cnt] = (byte) Integer.parseInt(ip.substring(pos,
2154: idx));
2155: cnt++;
2156: pos = idx + 1;
2157: }
2158: arr[cnt] = (byte) Integer.parseInt(ip.substring(pos));
2159:
2160: return arr;
2161: }
2162:
2163: /**
2164: * Sets the SOCKS server to use. The server will only be used
2165: * for new HTTPConnections created after this call and will not affect
2166: * currrent instances of HTTPConnection. A null or empty string host
2167: * parameter disables SOCKS.
2168: * <P>The code will try to determine the SOCKS version to use at
2169: * connection time. This might fail for a number of reasons, however,
2170: * in which case you must specify the version explicitly.
2171: *
2172: * @see #setSocksServer(java.lang.String, int, int)
2173: * @param host the host on which the proxy server resides. The port
2174: * used is the default port 1080.
2175: */
2176: public static void setSocksServer(String host) {
2177: setSocksServer(host, 1080);
2178: }
2179:
2180: /**
2181: * Sets the SOCKS server to use. The server will only be used
2182: * for new HTTPConnections created after this call and will not affect
2183: * currrent instances of HTTPConnection. A null or empty string host
2184: * parameter disables SOCKS.
2185: * <P>The code will try to determine the SOCKS version to use at
2186: * connection time. This might fail for a number of reasons, however,
2187: * in which case you must specify the version explicitly.
2188: *
2189: * @see #setSocksServer(java.lang.String, int, int)
2190: * @param host the host on which the proxy server resides.
2191: * @param port the port the proxy server is listening on.
2192: */
2193: public static void setSocksServer(String host, int port) {
2194: if (port <= 0)
2195: port = 1080;
2196:
2197: if (host == null || host.length() == 0)
2198: Default_Socks_client = null;
2199: else
2200: Default_Socks_client = new SocksClient(host, port);
2201: }
2202:
2203: /**
2204: * Sets the SOCKS server to use. The server will only be used
2205: * for new HTTPConnections created after this call and will not affect
2206: * currrent instances of HTTPConnection. A null or empty string host
2207: * parameter disables SOCKS.
2208: *
2209: * <P>In an application or using the Appletviewer an alternative to
2210: * this method is to set the following properties (either in the
2211: * properties file or on the command line):
2212: * <var>HTTPClient.socksHost</var>, <var>HTTPClient.socksPort</var>
2213: * and <var>HTTPClient.socksVersion</var>. Whether
2214: * <var>HTTPClient.socksHost</var> is set or not determines whether a
2215: * SOCKS server is used; if <var>HTTPClient.socksPort</var> is not set
2216: * it defaults to 1080; if <var>HTTPClient.socksVersion</var> is not
2217: * set an attempt will be made to automatically determine the version
2218: * used by the server.
2219: *
2220: * <P>Note: If you have also set a proxy server then a connection
2221: * will be made to the SOCKS server, which in turn then makes a
2222: * connection to the proxy server (possibly via other SOCKS servers),
2223: * which in turn makes the final connection.
2224: *
2225: * <P>If the proxy server is running SOCKS version 5 and requires
2226: * username/password authorization, and you wish to set
2227: * this authorization information in the code, then you may use the
2228: * <var>AuthorizationInfo.addAuthorization()</var> method to do so.
2229: * Specify the same <var>host</var> and <var>port</var> as in this
2230: * method, give the <var>scheme</var> "SOCKS5" and the <var>realm</var>
2231: * "USER/PASS", set the <var>cookie</var> to null and the
2232: * <var>params</var> to an array containing a single <var>NVPair</var>
2233: * in turn containing the username and password. Example:
2234: * <PRE>
2235: * NVPair[] up = { new NVPair(username, password) };
2236: * AuthorizationInfo.addAuthorization(host, port, "SOCKS5", "USER/PASS",
2237: * null, up);
2238: * </PRE>
2239: * If you have not given any authorization info and the proxy server
2240: * requires authorization then you will be prompted for the necessary
2241: * info via a popup the first time you do a request.
2242: *
2243: * @param host the host on which the proxy server resides.
2244: * @param port the port the proxy server is listening on.
2245: * @param version the SOCKS version the server is running. Currently
2246: * this must be '4' or '5'.
2247: * @exception SocksException If <var>version</var> is not '4' or '5'.
2248: */
2249: public static void setSocksServer(String host, int port, int version)
2250: throws SocksException {
2251: if (port <= 0)
2252: port = 1080;
2253:
2254: if (host == null || host.length() == 0)
2255: Default_Socks_client = null;
2256: else
2257: Default_Socks_client = new SocksClient(host, port, version);
2258: }
2259:
2260: /**
2261: * Removes the #... part. Returns the stripped name, or "" if either
2262: * the <var>file</var> is null or is the empty string (after stripping).
2263: *
2264: * @param file the name to strip
2265: * @return the stripped name
2266: */
2267: private final String stripRef(String file) {
2268: if (file == null)
2269: return "";
2270:
2271: int hash = file.indexOf('#');
2272: if (hash != -1)
2273: file = file.substring(0, hash);
2274:
2275: return file.trim();
2276: }
2277:
2278: // private helper methods
2279:
2280: /**
2281: * Sets up the request, creating the list of headers to send and
2282: * creating instances of the modules.
2283: *
2284: * @param method GET, POST, etc.
2285: * @param resource the resource
2286: * @param headers an array of headers to be used
2287: * @param entity the entity (or null)
2288: * @return the response.
2289: * @exception java.io.IOException when an exception is returned from
2290: * the socket.
2291: * @exception ModuleException if an exception is encountered in any module.
2292: */
2293: private HTTPResponse setupRequest(String method, String resource,
2294: NVPair[] headers, byte[] entity, HttpOutputStream stream)
2295: throws IOException, ModuleException {
2296: Request req = new Request(this , method, resource,
2297: mergedHeaders(headers), entity, stream, AllowUI);
2298: RequestList.addToEnd(req);
2299:
2300: try {
2301: HTTPResponse resp = new HTTPResponse(gen_mod_insts(),
2302: Timeout, req);
2303: handleRequest(req, resp, null, true);
2304: return resp;
2305: } finally {
2306: RequestList.remove(req);
2307: }
2308: }
2309:
2310: /**
2311: * This merges built-in default headers, user-specified default headers,
2312: * and method-specified headers. Method-specified take precedence over
2313: * user defaults, which take precedence over built-in defaults.
2314: *
2315: * The following headers are removed if found: "Host" and
2316: * "Content-length".
2317: *
2318: * @param spec the headers specified in the call to the method
2319: * @return an array consisting of merged headers.
2320: */
2321: private NVPair[] mergedHeaders(NVPair[] spec) {
2322: int spec_len = (spec != null ? spec.length : 0), defs_len;
2323: NVPair[] merged;
2324:
2325: synchronized (DefaultHeaders) {
2326: defs_len = (DefaultHeaders != null ? DefaultHeaders.length
2327: : 0);
2328: merged = new NVPair[spec_len + defs_len];
2329:
2330: // copy default headers
2331: System.arraycopy(DefaultHeaders, 0, merged, 0, defs_len);
2332: }
2333:
2334: // merge in selected headers
2335: int sidx, didx = defs_len;
2336: for (sidx = 0; sidx < spec_len; sidx++) {
2337: String s_name = spec[sidx].getName().trim();
2338: if (s_name.equalsIgnoreCase("Content-length")
2339: || s_name.equalsIgnoreCase("Host"))
2340: continue;
2341:
2342: int search;
2343: for (search = 0; search < didx; search++) {
2344: if (merged[search].getName().trim().equalsIgnoreCase(
2345: s_name))
2346: break;
2347: }
2348:
2349: merged[search] = spec[sidx];
2350: if (search == didx)
2351: didx++;
2352: }
2353:
2354: if (didx < merged.length)
2355: merged = Util.resizeArray(merged, didx);
2356:
2357: return merged;
2358: }
2359:
2360: /**
2361: * Generate an array of instances of the current modules.
2362: */
2363: private HTTPClientModule[] gen_mod_insts() {
2364: synchronized (ModuleList) {
2365: HTTPClientModule[] mod_insts = new HTTPClientModule[ModuleList
2366: .size()];
2367:
2368: for (int idx = 0; idx < ModuleList.size(); idx++) {
2369: Class mod = (Class) ModuleList.elementAt(idx);
2370: try {
2371: mod_insts[idx] = (HTTPClientModule) mod
2372: .newInstance();
2373: } catch (Exception e) {
2374: throw new Error(
2375: "HTTPClient Internal Error: could not "
2376: + "create instance of "
2377: + mod.getName() + " -\n" + e);
2378: }
2379: }
2380:
2381: return mod_insts;
2382: }
2383: }
2384:
2385: /**
2386: * handles the Request. First the request handler for each module is
2387: * is invoked, and then if no response was generated the request is
2388: * sent.
2389: *
2390: * @param req the Request
2391: * @param http_resp the HTTPResponse
2392: * @param resp the Response
2393: * @param usemodules if false then skip module loop
2394: * @exception IOException if any module or sendRequest throws it
2395: * @exception ModuleException if any module throws it
2396: */
2397: void handleRequest(Request req, HTTPResponse http_resp,
2398: Response resp, boolean usemodules) throws IOException,
2399: ModuleException {
2400: Response[] rsp_arr = { resp };
2401: HTTPClientModule[] modules = http_resp.getModules();
2402:
2403: // invoke requestHandler for each module
2404:
2405: if (usemodules)
2406: doModules: for (int idx = 0; idx < modules.length; idx++) {
2407: int sts = modules[idx].requestHandler(req, rsp_arr);
2408: switch (sts) {
2409: case REQ_CONTINUE: // continue processing
2410: break;
2411:
2412: case REQ_RESTART: // restart processing with first module
2413: idx = -1;
2414: continue doModules;
2415:
2416: case REQ_SHORTCIRC: // stop processing and send
2417: break doModules;
2418:
2419: case REQ_RESPONSE: // go to phase 2
2420: case REQ_RETURN: // return response immediately
2421: if (rsp_arr[0] == null)
2422: throw new Error(
2423: "HTTPClient Internal Error: no "
2424: + "response returned by module "
2425: + modules[idx].getClass()
2426: .getName());
2427: http_resp.set(req, rsp_arr[0]);
2428: if (req.getStream() != null)
2429: req.getStream().ignoreData(req);
2430: if (req.internal_subrequest)
2431: return;
2432: if (sts == REQ_RESPONSE)
2433: http_resp.handleResponse();
2434: else
2435: http_resp.init(rsp_arr[0]);
2436: return;
2437:
2438: case REQ_NEWCON_RST: // new connection
2439: if (req.internal_subrequest)
2440: return;
2441: req.getConnection().handleRequest(req, http_resp,
2442: rsp_arr[0], true);
2443: return;
2444:
2445: case REQ_NEWCON_SND: // new connection, send immediately
2446: if (req.internal_subrequest)
2447: return;
2448: req.getConnection().handleRequest(req, http_resp,
2449: rsp_arr[0], false);
2450: return;
2451:
2452: default: // not valid
2453: throw new Error(
2454: "HTTPClient Internal Error: invalid status"
2455: + " " + sts
2456: + " returned by module "
2457: + modules[idx].getClass().getName());
2458: }
2459: }
2460:
2461: if (req.internal_subrequest)
2462: return;
2463:
2464: // Send the request across the wire
2465:
2466: if (req.getStream() != null
2467: && req.getStream().getLength() == -1) {
2468: if (!ServProtVersKnown || ServerProtocolVersion < HTTP_1_1
2469: || no_chunked) {
2470: req.getStream().goAhead(req, null,
2471: http_resp.getTimeout());
2472: http_resp.set(req, req.getStream());
2473: } else {
2474: // add Transfer-Encoding header if necessary
2475: int idx;
2476: NVPair[] hdrs = req.getHeaders();
2477: for (idx = 0; idx < hdrs.length; idx++)
2478: if (hdrs[idx].getName().equalsIgnoreCase(
2479: "Transfer-Encoding"))
2480: break;
2481:
2482: if (idx == hdrs.length) {
2483: hdrs = Util.resizeArray(hdrs, idx + 1);
2484: hdrs[idx] = new NVPair("Transfer-Encoding",
2485: "chunked");
2486: req.setHeaders(hdrs);
2487: } else {
2488: String v = hdrs[idx].getValue();
2489: try {
2490: if (!Util.hasToken(v, "chunked"))
2491: hdrs[idx] = new NVPair("Transfer-Encoding",
2492: v + ", chunked");
2493: } catch (ParseException pe) {
2494: throw new IOException(pe.toString());
2495: }
2496: }
2497:
2498: http_resp.set(req, sendRequest(req, http_resp
2499: .getTimeout()));
2500: }
2501: } else
2502: http_resp
2503: .set(req, sendRequest(req, http_resp.getTimeout()));
2504:
2505: if (req.aborted)
2506: throw new IOException("Request aborted by user");
2507: }
2508:
2509: /** These mark the response to stall the next request on, if any */
2510: private Response early_stall = null;
2511: private Response late_stall = null;
2512: private Response prev_resp = null;
2513: /** This marks the socket output stream as still being used */
2514: private boolean output_finished = true;
2515:
2516: /**
2517: * sends the request over the line.
2518: *
2519: * @param req the request
2520: * @param con_timeout the timeout to use when establishing a socket
2521: * connection; an InterruptedIOException is thrown
2522: * if the procedure times out.
2523: * @return the response.
2524: * @exception IOException if thrown by the socket
2525: * @exception InterruptedIOException if the connection is not established
2526: * within the specified timeout
2527: * @exception ModuleException if any module throws it during the SSL-
2528: * tunneling handshake
2529: */
2530: Response sendRequest(Request req, int con_timeout)
2531: throws IOException, ModuleException {
2532: ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600);
2533: Response resp = null;
2534: boolean keep_alive;
2535:
2536: // The very first request is special in that we need its response
2537: // before any further requests may be made. This is to set things
2538: // like the server version.
2539:
2540: if (early_stall != null) {
2541: try {
2542: if (DebugConn)
2543: System.err
2544: .println("Conn: Early-stalling Request: "
2545: + req.getMethod() + " "
2546: + req.getRequestURI());
2547:
2548: synchronized (early_stall) {
2549: // wait till the response is received
2550: try {
2551: early_stall.getVersion();
2552: } catch (IOException ioe) {
2553: }
2554: early_stall = null;
2555: }
2556: } catch (NullPointerException npe) {
2557: }
2558: }
2559:
2560: String[] con_hdrs = assembleHeaders(req, hdr_buf);
2561:
2562: // determine if the connection should be kept alive after this
2563: // request
2564:
2565: try {
2566: if (ServerProtocolVersion >= HTTP_1_1
2567: && !Util.hasToken(con_hdrs[0], "close")
2568: || ServerProtocolVersion == HTTP_1_0
2569: && Util.hasToken(con_hdrs[0], "keep-alive"))
2570: keep_alive = true;
2571: else
2572: keep_alive = false;
2573: } catch (ParseException pe) {
2574: throw new IOException(pe.toString());
2575: }
2576:
2577: synchronized (this ) {
2578: // Sometimes we must stall the pipeline until the previous request
2579: // has been answered. However, if we are going to open up a new
2580: // connection anyway we don't really need to stall.
2581:
2582: if (late_stall != null) {
2583: if (input_demux != null || KeepAliveUnknown) {
2584: if (DebugConn)
2585: System.err.println("Conn: Stalling Request: "
2586: + req.getMethod() + " "
2587: + req.getRequestURI());
2588:
2589: try // wait till the response is received
2590: {
2591: late_stall.getVersion();
2592: if (KeepAliveUnknown)
2593: determineKeepAlive(late_stall);
2594: } catch (IOException ioe) {
2595: }
2596: }
2597:
2598: late_stall = null;
2599: }
2600:
2601: /* POSTs must not be pipelined because of problems if the connection
2602: * is aborted. Since it is generally impossible to know what urls
2603: * POST will influence it is impossible to determine if a sequence
2604: * of requests containing a POST is idempotent.
2605: * Also, for retried requests we don't want to pipeline either.
2606: */
2607: if ((req.getMethod().equals("POST") || req.dont_pipeline)
2608: && prev_resp != null && input_demux != null) {
2609: if (DebugConn)
2610: System.err.println("Conn: Stalling Request: "
2611: + req.getMethod() + " "
2612: + req.getRequestURI());
2613:
2614: try // wait till the response is received
2615: {
2616: prev_resp.getVersion();
2617: } catch (IOException ioe) {
2618: }
2619: }
2620:
2621: // If the previous request used an output stream, then wait till
2622: // all the data has been written
2623:
2624: if (!output_finished) {
2625: try {
2626: wait();
2627: } catch (InterruptedException ie) {
2628: throw new IOException(ie.toString());
2629: }
2630: }
2631:
2632: if (req.aborted)
2633: throw new IOException("Request aborted by user");
2634:
2635: int try_count = 3;
2636: /* what a hack! This is to handle the case where the server closes
2637: * the connection but we don't realize it until we try to send
2638: * something. The problem is that we only get IOException, but
2639: * we need a finer specification (i.e. whether it's an EPIPE or
2640: * something else); I don't trust relying on the message part
2641: * of IOException (which on SunOS/Solaris gives 'Broken pipe',
2642: * but what on Windoze/Mac?).
2643: */
2644:
2645: while (try_count-- > 0) {
2646: try {
2647: // get a client socket
2648:
2649: Socket sock;
2650: if (input_demux == null
2651: || (sock = input_demux.getSocket()) == null) {
2652: sock = getSocket(con_timeout);
2653:
2654: if (Protocol == HTTPS) {
2655: if (Proxy_Host != null) {
2656: Socket[] sarr = { sock };
2657: resp = enableSSLTunneling(sarr, req,
2658: con_timeout);
2659: if (resp != null) {
2660: resp.final_resp = true;
2661: return resp;
2662: }
2663: sock = sarr[0];
2664: }
2665: SSLSupport support = SSLSupport
2666: .getDefault();
2667: if (support != null)
2668: sock = support.createSocket(sock, Host,
2669: Port);
2670: //sock = new SSLSocket(sock);
2671: }
2672:
2673: input_demux = new StreamDemultiplexor(Protocol,
2674: sock, this );
2675: DemuxList.addToEnd(input_demux);
2676: KeepAliveReqLeft = KeepAliveReqMax;
2677: }
2678:
2679: if (req.aborted)
2680: throw new IOException("Request aborted by user");
2681:
2682: if (DebugConn) {
2683: System.err.println("Conn: Sending Request: ");
2684: System.err.println();
2685: hdr_buf.writeTo(System.err);
2686: }
2687:
2688: // Send headers
2689:
2690: OutputStream sock_out = sock.getOutputStream();
2691: if (haveMSLargeWritesBug)
2692: sock_out = new MSLargeWritesBugStream(sock_out);
2693:
2694: hdr_buf.writeTo(sock_out);
2695:
2696: // Wait for "100 Continue" status if necessary
2697:
2698: try {
2699: if (ServProtVersKnown
2700: && ServerProtocolVersion >= HTTP_1_1
2701: && Util.hasToken(con_hdrs[1],
2702: "100-continue")) {
2703: resp = new Response(
2704: req,
2705: (Proxy_Host != null && Protocol != HTTPS),
2706: input_demux);
2707: resp.timeout = 60;
2708: if (resp.getContinue() != 100)
2709: break;
2710: }
2711: } catch (ParseException pe) {
2712: throw new IOException(pe.toString());
2713: } catch (InterruptedIOException iioe) {
2714: } finally {
2715: if (resp != null)
2716: resp.timeout = 0;
2717: }
2718:
2719: // POST/PUT data
2720:
2721: if (req.getData() != null
2722: && req.getData().length > 0) {
2723: if (req.delay_entity > 0) {
2724: // wait for something on the network; check available()
2725: // roughly every 100 ms
2726:
2727: long num_units = req.delay_entity / 100;
2728: long one_unit = req.delay_entity
2729: / num_units;
2730:
2731: for (int idx = 0; idx < num_units; idx++) {
2732: if (input_demux.available(null) != 0)
2733: break;
2734: try {
2735: Thread.sleep(one_unit);
2736: } catch (InterruptedException ie) {
2737: }
2738: }
2739:
2740: if (input_demux.available(null) == 0)
2741: sock_out.write(req.getData()); // he's still waiting
2742: else
2743: keep_alive = false; // Uh oh!
2744: } else
2745: sock_out.write(req.getData());
2746: }
2747:
2748: if (req.getStream() != null)
2749: req.getStream().goAhead(req, sock_out, 0);
2750: else
2751: sock_out.flush();
2752:
2753: // get a new response.
2754: // Note: this does not do a read on the socket.
2755:
2756: if (resp == null)
2757: resp = new Response(
2758: req,
2759: (Proxy_Host != null && Protocol != HTTPS),
2760: input_demux);
2761: } catch (IOException ioe) {
2762: if (DebugConn) {
2763: System.err.print("Conn: ");
2764: ioe.printStackTrace();
2765: }
2766:
2767: closeDemux(ioe, true);
2768:
2769: if (try_count == 0
2770: || ioe instanceof UnknownHostException
2771: || ioe instanceof InterruptedIOException
2772: || req.aborted)
2773: throw ioe;
2774:
2775: if (DebugConn)
2776: System.err.println("Conn: Retrying request");
2777: continue;
2778: }
2779:
2780: break;
2781: }
2782:
2783: prev_resp = resp;
2784:
2785: // close the stream after this response if necessary
2786:
2787: if ((!KeepAliveUnknown && !DoesKeepAlive)
2788: || !keep_alive
2789: || (KeepAliveReqMax != -1 && KeepAliveReqLeft-- == 0)) {
2790: input_demux.markForClose(resp);
2791: input_demux = null;
2792: } else
2793: input_demux.restartTimer();
2794:
2795: if (DebugConn) {
2796: if (KeepAliveReqMax != -1)
2797: System.err
2798: .println("Conn: Number of requests left: "
2799: + KeepAliveReqLeft);
2800: }
2801:
2802: /* We don't pipeline the first request, as we need some info
2803: * about the server (such as which http version it complies with)
2804: */
2805: if (!ServProtVersKnown) {
2806: early_stall = resp;
2807: resp.markAsFirstResponse(req);
2808: }
2809:
2810: /* Also don't pipeline until we know if the server supports
2811: * keep-alive's or not.
2812: * Note: strictly speaking, HTTP/1.0 keep-alives don't mean we can
2813: * pipeline requests. I seem to remember some (beta?) version
2814: * of Netscape's Enterprise server which barfed if you tried
2815: * push requests down it's throat w/o waiting for the previous
2816: * response first. However, I've not been able to find such a
2817: * server lately, and so I'm taking the risk and assuming we
2818: * can in fact pipeline requests to HTTP/1.0 servers.
2819: */
2820: if (KeepAliveUnknown ||
2821: // We don't pipeline POST's ...
2822: !IdempotentSequence.methodIsIdempotent(req
2823: .getMethod()) || req.dont_pipeline || // Retries disable pipelining too
2824: NeverPipeline) // Emergency measure: prevent all pipelining
2825: {
2826: late_stall = resp;
2827: }
2828:
2829: /* If there is an output stream then just tell the other threads to
2830: * wait; the stream will notify() when it's done. If there isn't any
2831: * stream then wake up a waiting thread (if any).
2832: */
2833: if (req.getStream() != null)
2834: output_finished = false;
2835: else {
2836: output_finished = true;
2837: notify();
2838: }
2839:
2840: // Looks like were finally done
2841:
2842: if (DebugConn)
2843: System.err.println("Conn: Request sent");
2844: }
2845:
2846: return resp;
2847: }
2848:
2849: /**
2850: * Gets a socket. Creates a socket to the proxy if set, or else to the
2851: * actual destination.
2852: *
2853: * @param con_timeout if not 0 then start a new thread to establish the
2854: * the connection and join(con_timeout) it. If the
2855: * join() times out an InteruptedIOException is thrown.
2856: */
2857: private Socket getSocket(int con_timeout) throws IOException {
2858: Socket sock = null;
2859:
2860: String actual_host;
2861: int actual_port;
2862:
2863: if (Proxy_Host != null) {
2864: actual_host = Proxy_Host;
2865: actual_port = Proxy_Port;
2866: } else {
2867: actual_host = Host;
2868: actual_port = Port;
2869: }
2870:
2871: if (DebugConn)
2872: System.err.println("Conn: Creating Socket: " + actual_host
2873: + ":" + actual_port);
2874:
2875: if (con_timeout == 0) // normal connection establishment
2876: {
2877: if (Socks_client != null)
2878: sock = Socks_client.getSocket(actual_host, actual_port);
2879: else {
2880: // try all A records
2881: InetAddress[] addr_list = InetAddress
2882: .getAllByName(actual_host);
2883: for (int idx = 0; idx < addr_list.length; idx++) {
2884: try {
2885: sock = new Socket(addr_list[idx], actual_port);
2886: break; // success
2887: } catch (SocketException se) // should be NoRouteToHostException
2888: {
2889: if (idx == addr_list.length - 1)
2890: throw se; // we tried them all
2891: }
2892: }
2893: }
2894: } else {
2895: EstablishConnection con = new EstablishConnection(
2896: actual_host, actual_port, Socks_client);
2897: con.start();
2898: try {
2899: con.join((long) con_timeout);
2900: } catch (InterruptedException ie) {
2901: }
2902:
2903: if (con.getException() != null)
2904: throw con.getException();
2905: if ((sock = con.getSocket()) == null) {
2906: con.forget();
2907: if ((sock = con.getSocket()) == null)
2908: throw new InterruptedIOException(
2909: "Connection establishment timed out");
2910: }
2911: }
2912:
2913: return sock;
2914: }
2915:
2916: /**
2917: * Enable SSL Tunneling if we're talking to a proxy. See ietf draft
2918: * draft-luotonen-ssl-tunneling-03 for more info.
2919: *
2920: * @param sock the socket
2921: * @param req the request initiating this connection
2922: * @param timeout the timeout
2923: * @return the proxy's last response if unsuccessful, or null if
2924: * tunnel successfuly established
2925: * @exception IOException
2926: * @exception ModuleException
2927: */
2928: private Response enableSSLTunneling(Socket[] sock, Request req,
2929: int timeout) throws IOException, ModuleException {
2930: // copy User-Agent and Proxy-Auth headers from request
2931:
2932: Vector hdrs = new Vector();
2933: for (int idx = 0; idx < req.getHeaders().length; idx++) {
2934: String name = req.getHeaders()[idx].getName();
2935: if (name.equalsIgnoreCase("User-Agent")
2936: || name.equalsIgnoreCase("Proxy-Authorization"))
2937: hdrs.addElement(req.getHeaders()[idx]);
2938: }
2939:
2940: // create initial CONNECT subrequest
2941:
2942: NVPair[] h = new NVPair[hdrs.size()];
2943: hdrs.copyInto(h);
2944: Request connect = new Request(this , "CONNECT", Host + ":"
2945: + Port, h, null, null, req.allowUI());
2946: connect.internal_subrequest = true;
2947:
2948: ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600);
2949: HTTPResponse r = new HTTPResponse(gen_mod_insts(), timeout,
2950: connect);
2951:
2952: // send and handle CONNECT request until successful or tired
2953:
2954: Response resp = null;
2955:
2956: while (true) {
2957: handleRequest(connect, r, resp, true);
2958:
2959: hdr_buf.reset();
2960: assembleHeaders(connect, hdr_buf);
2961:
2962: if (DebugConn) {
2963: System.err
2964: .println("Conn: Sending SSL-Tunneling Subrequest: ");
2965: System.err.println();
2966: hdr_buf.writeTo(System.err);
2967: }
2968:
2969: // send CONNECT
2970:
2971: hdr_buf.writeTo(sock[0].getOutputStream());
2972:
2973: // return if successful
2974:
2975: resp = new Response(connect, sock[0].getInputStream());
2976: if (resp.getStatusCode() == 200)
2977: return null;
2978:
2979: // failed!
2980:
2981: // make life easy: read data and close socket
2982:
2983: try {
2984: resp.getData();
2985: } catch (IOException ioe) {
2986: }
2987: try {
2988: sock[0].close();
2989: } catch (IOException ioe) {
2990: }
2991:
2992: // handle response
2993:
2994: r.set(connect, resp);
2995: if (!r.handleResponse())
2996: return resp;
2997:
2998: sock[0] = getSocket(timeout);
2999: }
3000: }
3001:
3002: /**
3003: * This writes out the headers on the <var>hdr_buf</var>. It takes
3004: * special precautions for the following headers:
3005: * <DL>
3006: * <DT>Content-type<DI>This is only written if the request has an entity.
3007: * If the request has an entity and no content-type
3008: * header was given for the request it defaults to
3009: * "application/octet-stream"
3010: * <DT>Content-length<DI>This header is generated if the request has an
3011: * entity and the entity isn't being sent with the
3012: * Transfer-Encoding "chunked".
3013: * <DT>User-Agent <DI>If not present it will be generated with the current
3014: * HTTPClient version strings. Otherwise the version
3015: * string is appended to the given User-Agent string.
3016: * <DT>Connection <DI>This header is only written if no proxy is used.
3017: * If no connection header is specified and the server
3018: * is not known to understand HTTP/1.1 or later then
3019: * a "Connection: keep-alive" header is generated.
3020: * <DT>Proxy-Connection<DI>This header is only written if a proxy is used.
3021: * If no connection header is specified and the proxy
3022: * is not known to understand HTTP/1.1 or later then
3023: * a "Proxy-Connection: keep-alive" header is generated.
3024: * <DT>Keep-Alive <DI>This header is only written if the Connection or
3025: * Proxy-Connection header contains the Keep-Alive
3026: * token.
3027: * <DT>Expect <DI>If there is no entity and this header contains the
3028: * "100-continue" token then this token is removed.
3029: * before writing the header.
3030: * <DT>TE <DI>If this header does not exist, it is created; else
3031: * if the "trailers" token is not specified this token
3032: * is added; else the header is not touched.
3033: * </DL>
3034: *
3035: * Furthermore, it escapes various characters in request-URI.
3036: *
3037: * @param req the Request
3038: * @param hdr_buf the buffer onto which to write the headers
3039: * @return an array of headers; the first element contains the
3040: * the value of the Connection or Proxy-Connectin header,
3041: * the second element the value of the Expect header.
3042: * @exception IOException if writing on <var>hdr_buf</var> generates an
3043: * an IOException, or if an error occurs during
3044: * parsing of a header
3045: */
3046: private String[] assembleHeaders(Request req,
3047: ByteArrayOutputStream hdr_buf) throws IOException {
3048: DataOutputStream dataout = new DataOutputStream(hdr_buf);
3049: String[] con_hdrs = { "", "" };
3050: NVPair[] hdrs = req.getHeaders();
3051:
3052: // Generate request line and Host header
3053:
3054: String file = Util.escapeUnsafeChars(req.getRequestURI());
3055: if (Proxy_Host != null && Protocol != HTTPS
3056: && !file.equals("*"))
3057: dataout.writeBytes(req.getMethod() + " http://" + Host
3058: + ":" + Port + file + " " + RequestProtocolVersion
3059: + "\r\n");
3060: else
3061: dataout.writeBytes(req.getMethod() + " " + file + " "
3062: + RequestProtocolVersion + "\r\n");
3063:
3064: if (Port != 80)
3065: dataout.writeBytes("Host: " + Host + ":" + Port + "\r\n");
3066: else
3067: // Netscape-Enterprise has some bugs...
3068: dataout.writeBytes("Host: " + Host + "\r\n");
3069:
3070: // remember various headers
3071:
3072: int ct_idx = -1, ua_idx = -1, co_idx = -1, pc_idx = -1, ka_idx = -1, ex_idx = -1, te_idx = -1, tc_idx = -1, ug_idx = -1;
3073: for (int idx = 0; idx < hdrs.length; idx++) {
3074: String name = hdrs[idx].getName().trim();
3075: if (name.equalsIgnoreCase("Content-Type"))
3076: ct_idx = idx;
3077: else if (name.equalsIgnoreCase("User-Agent"))
3078: ua_idx = idx;
3079: else if (name.equalsIgnoreCase("Connection"))
3080: co_idx = idx;
3081: else if (name.equalsIgnoreCase("Proxy-Connection"))
3082: pc_idx = idx;
3083: else if (name.equalsIgnoreCase("Keep-Alive"))
3084: ka_idx = idx;
3085: else if (name.equalsIgnoreCase("Expect"))
3086: ex_idx = idx;
3087: else if (name.equalsIgnoreCase("TE"))
3088: te_idx = idx;
3089: else if (name.equalsIgnoreCase("Transfer-Encoding"))
3090: tc_idx = idx;
3091: else if (name.equalsIgnoreCase("Upgrade"))
3092: ug_idx = idx;
3093: }
3094:
3095: /*
3096: * What follows is the setup for persistent connections. We default
3097: * to doing persistent connections for both HTTP/1.0 and HTTP/1.1,
3098: * unless we're using a proxy server and HTTP/1.0 in which case we
3099: * must make sure we don't do persistence (because of the problem of
3100: * 1.0 proxies blindly passing the Connection header on).
3101: *
3102: * Note: there is a "Proxy-Connection" header for use with proxies.
3103: * This however is only understood by Netscape and Netapp caches.
3104: * Furthermore, it suffers from the same problem as the Connection
3105: * header in HTTP/1.0 except that at least two proxies must be
3106: * involved. But I've taken the risk now and decided to send the
3107: * Proxy-Connection header. If I get complaints I'll remove it again.
3108: *
3109: * In any case, with this header we can now modify the above to send
3110: * the Proxy-Connection header whenever we wouldn't send the normal
3111: * Connection header.
3112: */
3113:
3114: String co_hdr = null;
3115: if (!(ServProtVersKnown && ServerProtocolVersion >= HTTP_1_1 && co_idx == -1)) {
3116: if (co_idx == -1) { // no connection header given by user
3117: co_hdr = "Keep-Alive";
3118: con_hdrs[0] = "Keep-Alive";
3119: } else {
3120: con_hdrs[0] = hdrs[co_idx].getValue().trim();
3121: co_hdr = con_hdrs[0];
3122: }
3123:
3124: try {
3125: if (ka_idx != -1
3126: && Util.hasToken(con_hdrs[0], "keep-alive"))
3127: dataout.writeBytes("Keep-Alive: "
3128: + hdrs[ka_idx].getValue().trim() + "\r\n");
3129: } catch (ParseException pe) {
3130: throw new IOException(pe.toString());
3131: }
3132: }
3133:
3134: if ((Proxy_Host != null && Protocol != HTTPS)
3135: && !(ServProtVersKnown && ServerProtocolVersion >= HTTP_1_1)) {
3136: if (co_hdr != null) {
3137: dataout.writeBytes("Proxy-Connection: ");
3138: dataout.writeBytes(co_hdr);
3139: dataout.writeBytes("\r\n");
3140: co_hdr = null;
3141: }
3142: }
3143:
3144: if (co_hdr != null) {
3145: try {
3146: if (!Util.hasToken(co_hdr, "TE"))
3147: co_hdr += ", TE";
3148: } catch (ParseException pe) {
3149: throw new IOException(pe.toString());
3150: }
3151: } else
3152: co_hdr = "TE";
3153:
3154: if (ug_idx != -1)
3155: co_hdr += ", Upgrade";
3156:
3157: if (co_hdr != null) {
3158: dataout.writeBytes("Connection: ");
3159: dataout.writeBytes(co_hdr);
3160: dataout.writeBytes("\r\n");
3161: }
3162:
3163: // handle TE header
3164:
3165: if (te_idx != -1) {
3166: dataout.writeBytes("TE: ");
3167: Vector pte;
3168: try {
3169: pte = Util.parseHeader(hdrs[te_idx].getValue());
3170: } catch (ParseException pe) {
3171: throw new IOException(pe.toString());
3172: }
3173:
3174: if (!pte.contains(new HttpHeaderElement("trailers")))
3175: dataout.writeBytes("trailers, ");
3176:
3177: dataout.writeBytes(hdrs[te_idx].getValue().trim() + "\r\n");
3178: } else
3179: dataout.writeBytes("TE: trailers\r\n");
3180:
3181: // User-Agent
3182:
3183: if (ua_idx != -1)
3184: dataout.writeBytes("User-Agent: "
3185: + hdrs[ua_idx].getValue().trim() + " " + version
3186: + "\r\n");
3187: else
3188: dataout.writeBytes("User-Agent: " + version + "\r\n");
3189:
3190: // Write out any headers left
3191:
3192: for (int idx = 0; idx < hdrs.length; idx++) {
3193: if (idx != ct_idx && idx != ua_idx && idx != co_idx
3194: && idx != pc_idx && idx != ka_idx && idx != ex_idx
3195: && idx != te_idx)
3196: dataout.writeBytes(hdrs[idx].getName().trim() + ": "
3197: + hdrs[idx].getValue().trim() + "\r\n");
3198: }
3199:
3200: // Handle Content-type, Content-length and Expect headers
3201:
3202: if (req.getData() != null || req.getStream() != null) {
3203: dataout.writeBytes("Content-type: ");
3204: if (ct_idx != -1)
3205: dataout.writeBytes(hdrs[ct_idx].getValue().trim());
3206: else
3207: dataout.writeBytes("application/octet-stream");
3208: dataout.writeBytes("\r\n");
3209:
3210: if (req.getData() != null)
3211: dataout.writeBytes("Content-length: "
3212: + req.getData().length + "\r\n");
3213: else if (req.getStream().getLength() != -1 && tc_idx == -1)
3214: dataout.writeBytes("Content-length: "
3215: + req.getStream().getLength() + "\r\n");
3216:
3217: if (ex_idx != -1) {
3218: con_hdrs[1] = hdrs[ex_idx].getValue().trim();
3219: dataout.writeBytes("Expect: " + con_hdrs[1] + "\r\n");
3220: }
3221: } else if (ex_idx != -1) {
3222: Vector expect_tokens;
3223: try {
3224: expect_tokens = Util.parseHeader(hdrs[ex_idx]
3225: .getValue());
3226: } catch (ParseException pe) {
3227: throw new IOException(pe.toString());
3228: }
3229:
3230: // remove any 100-continue tokens
3231:
3232: HttpHeaderElement cont = new HttpHeaderElement(
3233: "100-continue");
3234: while (expect_tokens.removeElement(cont))
3235: ;
3236:
3237: // write out header if any tokens left
3238:
3239: if (!expect_tokens.isEmpty()) {
3240: con_hdrs[1] = Util.assembleHeader(expect_tokens);
3241: dataout.writeBytes("Expect: " + con_hdrs[1] + "\r\n");
3242: }
3243: }
3244:
3245: dataout.writeBytes("\r\n"); // end of header
3246:
3247: return con_hdrs;
3248: }
3249:
3250: /**
3251: * The very first request is special in that we use it to figure out the
3252: * protocol version the server (or proxy) is compliant with.
3253: *
3254: * @return true if all went fine, false if the request needs to be resent
3255: * @exception IOException if any exception is thrown by the response
3256: */
3257: boolean handleFirstRequest(Request req, Response resp)
3258: throws IOException {
3259: // read response headers to get protocol version used by
3260: // the server.
3261:
3262: ServerProtocolVersion = String2ProtVers(resp.getVersion());
3263: ServProtVersKnown = true;
3264:
3265: /* We need to treat connections through proxies specially, because
3266: * many HTTP/1.0 proxies do not downgrade an HTTP/1.1 response
3267: * version to HTTP/1.0 (i.e. when we are talking to an HTTP/1.1
3268: * server through an HTTP/1.0 proxy we are mislead to thinking we're
3269: * talking to an HTTP/1.1 proxy). We use the absence of the Via
3270: * header to detect whether we're talking to an HTTP/1.0 proxy.
3271: * However, this only works when the chain contains only HTTP/1.0
3272: * proxies; if you have <client - 1.0 proxy - 1.1 proxy - server>
3273: * then this will fail too. Unfortunately there seems to be no way
3274: * to reliably detect broken HTTP/1.0 proxies...
3275: */
3276: if ((Proxy_Host != null && Protocol != HTTPS)
3277: && resp.getHeader("Via") == null)
3278: ServerProtocolVersion = HTTP_1_0;
3279:
3280: if (DebugConn)
3281: System.err.println("Conn: Protocol Version established: "
3282: + ProtVers2String(ServerProtocolVersion));
3283:
3284: // some (buggy) servers return an error status if they get a
3285: // version they don't comprehend
3286:
3287: if (ServerProtocolVersion == HTTP_1_0
3288: && (resp.getStatusCode() == 400 || resp.getStatusCode() == 500)) {
3289: input_demux.markForClose(resp);
3290: input_demux = null;
3291: RequestProtocolVersion = "HTTP/1.0";
3292: return false;
3293: }
3294:
3295: return true;
3296: }
3297:
3298: private void determineKeepAlive(Response resp) throws IOException {
3299: // try and determine if this server does keep-alives
3300:
3301: String con;
3302:
3303: try {
3304: if (ServerProtocolVersion >= HTTP_1_1
3305: || ((((Proxy_Host == null || Protocol == HTTPS) && (con = resp
3306: .getHeader("Connection")) != null) || ((Proxy_Host != null && Protocol != HTTPS) && (con = resp
3307: .getHeader("Proxy-Connection")) != null)) && Util
3308: .hasToken(con, "keep-alive"))) {
3309: DoesKeepAlive = true;
3310:
3311: if (DebugConn)
3312: System.err.println("Conn: Keep-Alive enabled");
3313:
3314: KeepAliveUnknown = false;
3315: } else if (resp.getStatusCode() < 400)
3316: KeepAliveUnknown = false;
3317:
3318: // get maximum number of requests
3319:
3320: if (DoesKeepAlive && ServerProtocolVersion == HTTP_1_0
3321: && (con = resp.getHeader("Keep-Alive")) != null) {
3322: HttpHeaderElement max = Util.getElement(Util
3323: .parseHeader(con), "max");
3324: if (max != null && max.getValue() != null) {
3325: KeepAliveReqMax = Integer.parseInt(max.getValue());
3326: KeepAliveReqLeft = KeepAliveReqMax;
3327:
3328: if (DebugConn)
3329: System.err
3330: .println("Conn: Max Keep-Alive requests: "
3331: + KeepAliveReqMax);
3332: }
3333: }
3334: } catch (ParseException pe) {
3335: } catch (NumberFormatException nfe) {
3336: } catch (ClassCastException cce) {
3337: }
3338: }
3339:
3340: synchronized void outputFinished() {
3341: output_finished = true;
3342: notify();
3343: }
3344:
3345: synchronized void closeDemux(IOException ioe, boolean was_reset) {
3346: if (input_demux != null)
3347: input_demux.close(ioe, was_reset);
3348:
3349: early_stall = null;
3350: late_stall = null;
3351: prev_resp = null;
3352: }
3353:
3354: final static String ProtVers2String(int prot_vers) {
3355: return "HTTP/" + (prot_vers >>> 16) + "."
3356: + (prot_vers & 0xFFFF);
3357: }
3358:
3359: final static int String2ProtVers(String prot_vers) {
3360: String vers = prot_vers.substring(5);
3361: int dot = vers.indexOf('.');
3362: return Integer.parseInt(vers.substring(0, dot)) << 16
3363: | Integer.parseInt(vers.substring(dot + 1));
3364: }
3365:
3366: /**
3367: * Generates a string of the form protocol://host.domain:port .
3368: *
3369: * @return the string
3370: */
3371: public String toString() {
3372: return getProtocol()
3373: + "://"
3374: + getHost()
3375: + (getPort() != URI.defaultPort(getProtocol()) ? ":"
3376: + getPort() : "");
3377: }
3378:
3379: private class EstablishConnection extends Thread {
3380: String actual_host;
3381: int actual_port;
3382: IOException exception;
3383: Socket sock;
3384: SocksClient Socks_client;
3385: boolean close;
3386:
3387: EstablishConnection(String host, int port, SocksClient socks) {
3388: super ("EstablishConnection (" + host + ":" + port + ")");
3389: try {
3390: setDaemon(true);
3391: } catch (SecurityException se) {
3392: } // Oh well...
3393:
3394: actual_host = host;
3395: actual_port = port;
3396: Socks_client = socks;
3397:
3398: exception = null;
3399: sock = null;
3400: close = false;
3401: }
3402:
3403: public void run() {
3404: try {
3405: if (Socks_client != null)
3406: sock = Socks_client.getSocket(actual_host,
3407: actual_port);
3408: else {
3409: // try all A records
3410: InetAddress[] addr_list = InetAddress
3411: .getAllByName(actual_host);
3412: for (int idx = 0; idx < addr_list.length; idx++) {
3413: try {
3414: sock = new Socket(addr_list[idx],
3415: actual_port);
3416: break; // success
3417: } catch (SocketException se) // should be NoRouteToHostException
3418: {
3419: if (idx == addr_list.length - 1 || close)
3420: throw se; // we tried them all
3421: }
3422: }
3423: }
3424: } catch (IOException ioe) {
3425: exception = ioe;
3426: }
3427:
3428: if (close && sock != null) {
3429: try {
3430: sock.close();
3431: } catch (IOException ioe) {
3432: }
3433: sock = null;
3434: }
3435: }
3436:
3437: IOException getException() {
3438: return exception;
3439: }
3440:
3441: Socket getSocket() {
3442: return sock;
3443: }
3444:
3445: void forget() {
3446: close = true;
3447: }
3448: }
3449:
3450: /**
3451: * M$ has yet another bug in their WinSock: if you try to write too much
3452: * data at once it'll hang itself. This filter therefore splits big writes
3453: * up into multiple writes of at most 20K.
3454: */
3455: private class MSLargeWritesBugStream extends FilterOutputStream {
3456: private final int CHUNK_SIZE = 20000;
3457:
3458: MSLargeWritesBugStream(OutputStream os) {
3459: super (os);
3460: }
3461:
3462: public void write(byte[] b, int off, int len)
3463: throws IOException {
3464: while (len > CHUNK_SIZE) {
3465: out.write(b, off, CHUNK_SIZE);
3466: off += CHUNK_SIZE;
3467: len -= CHUNK_SIZE;
3468: }
3469: out.write(b, off, len);
3470: }
3471: }
3472: }
|