0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.io.j2me.http;
0028:
0029: /**
0030: * A class representing a http connection. An http connection consists of
0031: * stream connection as well as input and output streams for read/write data to
0032: * and from a web server. This version supports HTTP1.1 persistent connections
0033: * allowing connects to be shared from a connection pool. This pool and the
0034: * maximum number of connections can be configured for a particular platform.
0035: * Proxy connections are also allowed through this interface.
0036: *
0037: * <p> The actual connection to the web server does not take place until the
0038: * application needs an, (1) input stream, (2) flush data, (3)request some
0039: * header info or closes the connection (with outstanding data in the
0040: * output stream). Because of this issue the state transition must allow for
0041: * some flexibility to move backwards for WRITE state conditions.
0042: *
0043: * <p> Persistent connections are provided through the use of a connection
0044: * pool that tracks the connect status. There are maximum threshold values
0045: * defined and these values can be overriden using property key/value pars.
0046: * The connection pool provides a synchronized interface for managing the
0047: * maximum configurable connections. Persistent connections will only be
0048: * supported for HTTP1.1 connections - otherwise the connections will be
0049: * closed and disregarded after its done (HTTP1.0 behavior).
0050: *
0051: * <p> This class extends the ConnectionBaseAdapter where Connector type
0052: * objects (like this) use various features. Output and Input streams are
0053: * created and managed in the adapter class.
0054: *
0055: * <p> The reading and writing of data through the input and output streams
0056: * are configured, buffered and managed depending on the ability of a
0057: * platform to read/write on those streams.
0058: *
0059: */
0060:
0061: import java.io.IOException;
0062: import java.io.InputStream;
0063: import java.io.InterruptedIOException;
0064: import java.io.OutputStream;
0065: import java.io.DataInputStream;
0066: import java.io.DataOutputStream;
0067: import java.io.ByteArrayOutputStream;
0068:
0069: import java.util.Enumeration;
0070: import java.util.Hashtable;
0071:
0072: import javax.microedition.io.SocketConnection;
0073: import javax.microedition.io.StreamConnection;
0074: import javax.microedition.io.HttpConnection;
0075: import javax.microedition.io.Connector;
0076: import javax.microedition.io.Connection;
0077: import javax.microedition.io.ConnectionNotFoundException;
0078:
0079: import com.sun.midp.main.Configuration;
0080:
0081: import com.sun.midp.io.ConnectionBaseAdapter;
0082: import com.sun.midp.io.HttpUrl;
0083: import com.sun.midp.io.NetworkConnectionBase;
0084:
0085: import com.sun.midp.midlet.MIDletStateHandler;
0086: import com.sun.midp.midlet.MIDletSuite;
0087:
0088: import com.sun.midp.security.SecurityToken;
0089: import com.sun.midp.security.Permissions;
0090: import com.sun.midp.security.SecurityInitializer;
0091: import com.sun.midp.security.ImplicitlyTrustedClass;
0092:
0093: import com.sun.midp.util.DateParser;
0094: import com.sun.midp.util.Properties;
0095:
0096: /**
0097: * This class implements the necessary functionality
0098: * for an HTTP connection.
0099: */
0100: public class Protocol extends ConnectionBaseAdapter implements
0101: HttpConnection {
0102:
0103: /** HTTP version string to use with all outgoing HTTP requests. */
0104: protected static final String HTTP_VERSION = "HTTP/1.1";
0105: /** Where to start the data in the output buffer. */
0106: private static final int HTTP_OUTPUT_DATA_OFFSET = 24;
0107: /** How must extra room for the chunk terminator. */
0108: private static final int HTTP_OUTPUT_EXTRA_ROOM = 8;
0109:
0110: /**
0111: * Inner class to request security token from SecurityInitializer.
0112: * SecurityInitializer should be able to check this inner class name.
0113: */
0114: static private class SecurityTrusted implements
0115: ImplicitlyTrustedClass {
0116: };
0117:
0118: /** This class has a different security domain than the MIDlet suite */
0119: private static SecurityToken classSecurityToken = SecurityInitializer
0120: .requestToken(new SecurityTrusted());
0121:
0122: /** Default size for input buffer. */
0123: private static int inputBufferSize = 256;
0124: /** Default size for output buffer. */
0125: private static int outputBufferSize = 2048;
0126: /** How much data can be put in the output buffer. */
0127: private static int outputDataSize;
0128: /** The "host:port" value to use for HTTP proxied requests. */
0129: private static String http_proxy;
0130: /** Maximum number of persistent connections. */
0131: private static int maxNumberOfPersistentConnections = 4;
0132: /** Connection linger time in the pool, default 60 seconds. */
0133: private static long connectionLingerTime = 60000;
0134: /** Persistent connection pool. */
0135: protected static StreamConnectionPool connectionPool;
0136: /** True if com.sun.midp.io.http.force_non_persistent = true. */
0137: private static boolean nonPersistentFlag;
0138: /**
0139: * The methods other than openPrim need to know that the
0140: * permission occurred.
0141: */
0142: private boolean permissionChecked;
0143: /** True if the owner of this connection is trusted. */
0144: private boolean ownerTrusted;
0145:
0146: /** Get the configuration values for this class. */
0147: static {
0148: String prop;
0149: int temp;
0150:
0151: /*
0152: * Get the proxy here instead of the connector,
0153: * so when this method subclassed by HTTPS http_proxy will be null
0154: * and the proxy will not be added into the request.
0155: */
0156: http_proxy = Configuration
0157: .getProperty("com.sun.midp.io.http.proxy");
0158:
0159: /*
0160: * CR#4455443 - allows for configuration options to shut off
0161: * the persistent connection feature for http
0162: */
0163: String flag = Configuration
0164: .getProperty("com.sun.midp.io.http.force_non_persistent");
0165: if ((flag != null) && (flag.equals("true"))) {
0166: nonPersistentFlag = true;
0167: }
0168:
0169: /*
0170: * Get the maximum number of persistent connections
0171: * from the configuration file if there is one.
0172: */
0173: maxNumberOfPersistentConnections = Configuration
0174: .getNonNegativeIntProperty(
0175: "com.sun.midp.io.http.max_persistent_connections",
0176: maxNumberOfPersistentConnections);
0177:
0178: // Get how long a "not in use" connection should stay in the pool.
0179: connectionLingerTime = (long) Configuration
0180: .getNonNegativeIntProperty(
0181: "com.sun.midp.io.http.persistent_connection_linger_time",
0182: (int) connectionLingerTime);
0183:
0184: connectionPool = new StreamConnectionPool(
0185: maxNumberOfPersistentConnections, connectionLingerTime);
0186:
0187: /*
0188: * Get the buffer sizes from the configuration file.
0189: * 0 for the input buffer size shuts off input buffering.
0190: * Output buffer must always be positive.
0191: */
0192: inputBufferSize = Configuration.getNonNegativeIntProperty(
0193: "com.sun.midp.io.http.input_buffer_size",
0194: inputBufferSize);
0195:
0196: temp = outputBufferSize;
0197: outputBufferSize = Configuration.getPositiveIntProperty(
0198: "com.sun.midp.io.http.output_buffer_size",
0199: outputBufferSize);
0200: if (outputBufferSize <= (HTTP_OUTPUT_DATA_OFFSET + HTTP_OUTPUT_EXTRA_ROOM)) {
0201: outputBufferSize = temp;
0202: }
0203:
0204: outputDataSize = outputBufferSize - HTTP_OUTPUT_DATA_OFFSET
0205: - HTTP_OUTPUT_EXTRA_ROOM;
0206: }
0207:
0208: /** The protocol (or scheme) for the URL of the connection. */
0209: protected String protocol;
0210: /** Default port number for this protocol. */
0211: protected int default_port;
0212: /** Parsed Url. */
0213: protected HttpUrl url;
0214: /** url.host + ":" + url.port. */
0215: protected String hostAndPort;
0216:
0217: /** Numeric code returned from HTTP response header. */
0218: protected int responseCode;
0219: /** Message string from HTTP response header. */
0220: protected String responseMsg;
0221:
0222: /** Collection of request headers as name/value pairs. */
0223: protected Properties reqProperties;
0224:
0225: /** Collection of response headers as name/value pairs. */
0226: protected Properties headerFields;
0227:
0228: /** HTTP method type for the current request. */
0229: protected String method;
0230:
0231: /*
0232: * The streams from the underlying socket connection.
0233: */
0234: /** Low level socket connection used for the HTTP requests. */
0235: private StreamConnection streamConnection;
0236: /** Low level socket output stream. */
0237: private DataOutputStream streamOutput;
0238: /** Low level socket input stream. */
0239: private DataInputStream streamInput;
0240: /** A shared temporary header buffer. */
0241: private StringBuffer stringbuffer;
0242: /** HTTP version string set with all incoming HTTP responses. */
0243: private String httpVer = null;
0244: /** Used when appl calls setRequestProperty("Connection", "close"). */
0245: private boolean ConnectionCloseFlag;
0246: /** Content-Length from response header, or -1 if missing. */
0247: private int contentLength = -1;
0248: /**
0249: * Total number of bytes in the current chunk or content-length when
0250: * data is sent as one big chunk.
0251: */
0252: private int chunksize = -1;
0253: /**
0254: * Number of bytes read from the stream for non-chunked data or
0255: * the bytes read from the current chunk.
0256: */
0257: private int totalbytesread;
0258: /** True if Transfer-Encoding: chunkedIn. */
0259: private boolean chunkedIn;
0260: /** True if Transfer-Encoding: chunkedOut. */
0261: private boolean chunkedOut;
0262: /** True after the first chunk has been sent. */
0263: private boolean firstChunkSent;
0264: /** True if the request is being sent. */
0265: private boolean sendingRequest;
0266: /** True if the entire request has been sent to the server. */
0267: private boolean requestFinished;
0268: /** True if eof seen. */
0269: private boolean eof;
0270: /** Internal stream buffer to minimize the number of TCP socket reads. */
0271: private byte[] readbuf;
0272: /** Number of bytes left in internal input stream buffer. */
0273: private int bytesleft;
0274: /** Number of bytes read from the internal input stream buffer. */
0275: private int bytesread;
0276: /** Buffered data output for content length calculation. */
0277: private byte[] writebuf;
0278: /** Number of bytes of data that need to be written from the buffer. */
0279: private int bytesToWrite;
0280: /** Collection of "Proxy-" headers as name/value pairs. */
0281: private Properties proxyHeaders = new Properties();
0282: /** Last handshake error. */
0283: private byte handshakeError;
0284: /**
0285: * Holds the state the readBytes call. So if close is called in another
0286: * thread than the read thread the close will be directly on the stream,
0287: * instead of putting the connection back in the persistent connection
0288: * pool, forcing an IOException on the read thread.
0289: */
0290: private boolean readInProgress;
0291:
0292: /**
0293: * Create a new instance of this class and intialize variables.
0294: * Initially an http connection is unconnected to the network.
0295: */
0296: public Protocol() {
0297: reqProperties = new Properties();
0298: headerFields = new Properties();
0299: stringbuffer = new StringBuffer(32);
0300:
0301: method = GET;
0302: responseCode = -1;
0303: protocol = "http";
0304: default_port = 80;
0305:
0306: if (nonPersistentFlag) {
0307: ConnectionCloseFlag = true;
0308: }
0309:
0310: readbuf = new byte[inputBufferSize];
0311: }
0312:
0313: /**
0314: * Sets up the state of the connection, but
0315: * does not actually connect to the server until there's something
0316: * to do. This is method by system classes.
0317: *
0318: * @param token Token with the HTTP permission set to the allowed level
0319: * @param fullUrl Full URL of the connection
0320: *
0321: * @return reference to this connection
0322: *
0323: * @exception IllegalArgumentException If a parameter is invalid.
0324: * @exception ConnectionNotFoundException If the connection cannot be
0325: * found.
0326: * @exception IOException If some other kind of I/O error occurs.
0327: */
0328: public Connection openPrim(SecurityToken token, String fullUrl)
0329: throws IOException, IllegalArgumentException,
0330: ConnectionNotFoundException {
0331:
0332: checkIfPermissionAllowed(token);
0333: return open(new HttpUrl(fullUrl), Connector.READ_WRITE);
0334: }
0335:
0336: /**
0337: * Sets up the state of the connection, but
0338: * does not actually connect to the server until there's something
0339: * to do.
0340: *
0341: * @param name The URL for the connection, without the
0342: * without the protocol part.
0343: * @param mode The access mode, ignored
0344: * @param timeouts A flag to indicate that the called wants
0345: * timeout exceptions, ignored
0346: *
0347: * @return reference to this connection
0348: *
0349: * @exception IllegalArgumentException If a parameter is invalid.
0350: * @exception ConnectionNotFoundException If the connection cannot be
0351: * found.
0352: * @exception IOException If some other kind of I/O error occurs.
0353: */
0354: public Connection openPrim(String name, int mode, boolean timeouts)
0355: throws IOException, IllegalArgumentException,
0356: ConnectionNotFoundException {
0357:
0358: checkForPermission(name);
0359: return open(new HttpUrl(protocol, name), mode);
0360: }
0361:
0362: /**
0363: * Sets up the state of the connection, but
0364: * does not actually connect to the server until there's something
0365: * to do.
0366: *
0367: * @param theUrl URL object
0368: * @param mode The access mode, ignored
0369: * timeout exceptions, ignored
0370: *
0371: * @return reference to this connection
0372: *
0373: * @exception IllegalArgumentException If a parameter is invalid.
0374: * @exception ConnectionNotFoundException If the connection cannot be
0375: * found.
0376: * @exception IOException If some other kind of I/O error occurs.
0377: */
0378: private Connection open(HttpUrl theUrl, int mode)
0379: throws IOException, IllegalArgumentException,
0380: ConnectionNotFoundException {
0381:
0382: url = theUrl;
0383:
0384: initStreamConnection(mode);
0385:
0386: if (url.port == -1) {
0387: url.port = default_port;
0388: }
0389:
0390: if (url.host == null) {
0391: throw new IllegalArgumentException("missing host in URL");
0392: }
0393:
0394: hostAndPort = url.host + ":" + url.port;
0395:
0396: return this ;
0397: }
0398:
0399: /**
0400: * Check if the required permission has been set to allowed. Allowed
0401: * is required because this method will not prompt the user. Only
0402: * trusted callers have the a permission set to the allowed level.
0403: *
0404: * @param token token with the HTTP permission set to the allowed level
0405: */
0406: private void checkIfPermissionAllowed(SecurityToken token) {
0407: token.checkIfPermissionAllowed(Permissions.HTTP);
0408: ownerTrusted = true;
0409: permissionChecked = true;
0410: }
0411:
0412: /**
0413: * Check for the required permission.
0414: *
0415: * @param name name of resource to insert into the permission question
0416: *
0417: * @exception IOInterruptedException if another thread interrupts the
0418: * calling thread while this method is waiting to preempt the
0419: * display.
0420: */
0421: private void checkForPermission(String name)
0422: throws InterruptedIOException {
0423: MIDletStateHandler midletStateHandler;
0424: MIDletSuite midletSuite;
0425:
0426: midletStateHandler = MIDletStateHandler.getMidletStateHandler();
0427: midletSuite = midletStateHandler.getMIDletSuite();
0428:
0429: if (midletSuite == null) {
0430: throw new IllegalStateException("This class can't be used "
0431: + "before a suite is started.");
0432: }
0433:
0434: name = protocol + ":" + name;
0435:
0436: try {
0437: midletSuite.checkForPermission(Permissions.HTTP, name);
0438: ownerTrusted = midletSuite.isTrusted();
0439: permissionChecked = true;
0440: } catch (InterruptedException ie) {
0441: throw new InterruptedIOException(
0442: "Interrupted while trying to ask the user permission");
0443: }
0444: }
0445:
0446: /**
0447: * Open the input stream if it has not already been opened.
0448: *
0449: * @exception IOException is thrown if it has already been opened.
0450: * @return input stream for the current connection
0451: */
0452: public InputStream openInputStream() throws IOException {
0453: InputStream in;
0454:
0455: /*
0456: * Call into parent to create input stream passed back to the user
0457: */
0458: in = super .openInputStream();
0459:
0460: /*
0461: * Send a request to the web server if there wasn't one
0462: * sent already
0463: */
0464: sendRequest();
0465:
0466: return in;
0467: }
0468:
0469: /**
0470: * Open the output stream if it has not already been opened.
0471: *
0472: * @exception IOException is thrown if it has already been opened.
0473: * @return output stream for the current connection
0474: */
0475: public OutputStream openOutputStream() throws IOException {
0476: OutputStream out;
0477:
0478: /*
0479: * call into parent to create output stream passed back to the user
0480: */
0481: out = super .openOutputStream();
0482:
0483: /*
0484: * Create a byte array output stream for output buffering
0485: * once the user calls flush() this gets written to stream
0486: */
0487: writebuf = new byte[outputBufferSize];
0488:
0489: return out;
0490: }
0491:
0492: /**
0493: * Reads up to <code>len</code> bytes of data from the input stream into
0494: * an array of bytes.
0495: * This method reads NonChunked http connection input streams.
0496: * This method can only be called after the InputStream setup is complete.
0497: *
0498: * @param b the buffer into which the data is read.
0499: * @param off the start offset in array <code>b</code>
0500: * at which the data is written.
0501: * @param len the maximum number of bytes to read.
0502: * @return the total number of bytes read into the buffer, or
0503: * <code>-1</code> if there is no more data because the end of
0504: * the stream has been reached.
0505: * @exception IOException if an I/O error occurs.
0506: */
0507: protected int readBytes(byte b[], int off, int len)
0508: throws IOException {
0509:
0510: int rc;
0511:
0512: /*
0513: * Be consistent about returning EOF once encountered.
0514: */
0515: if (eof) {
0516: return (-1);
0517: }
0518:
0519: /*
0520: * The InputStream close behavior will be different if close is called
0521: * from another thread when reading.
0522: */
0523: synchronized (streamInput) {
0524: readInProgress = true;
0525: }
0526:
0527: try {
0528: /*
0529: * If the http connection is chunked, call the readBytesChunked
0530: * method
0531: */
0532: if (chunkedIn || chunksize > 0) {
0533: /*
0534: * Non-chunked data of known length is treated as one big chunk
0535: */
0536: return readBytesChunked(b, off, len);
0537: }
0538:
0539: /*
0540: * Non-chunked unknown length
0541: */
0542:
0543: if (bytesleft == 0) {
0544: /*
0545: * the internal input stream buffer is empty, read from the
0546: * stream
0547: */
0548: if (len >= inputBufferSize) {
0549: /*
0550: * No need to buffer, if the caller has given a big buffer.
0551: */
0552: rc = streamInput.read(b, off, len);
0553: } else {
0554: rc = streamInput.read(readbuf, 0, inputBufferSize);
0555: bytesleft = rc;
0556: bytesread = 0;
0557: }
0558:
0559: if (rc == -1) {
0560: /*
0561: * The next call to this method should not read.
0562: */
0563: eof = true;
0564: return -1;
0565: }
0566:
0567: totalbytesread += rc;
0568:
0569: if (bytesleft == 0) {
0570: /*
0571: * The data was read directly into the caller's buffer.
0572: */
0573: return rc;
0574: }
0575: }
0576:
0577: rc = readFromBuffer(b, off, len);
0578: return rc;
0579: } finally {
0580: synchronized (streamInput) {
0581: readInProgress = false;
0582: }
0583: }
0584: }
0585:
0586: /**
0587: * Reads up to <code>len</code> bytes of data from the internal buffer into
0588: * an array of bytes.
0589: *
0590: * @param b the buffer into which the data is read.
0591: * @param off the start offset in array <code>b</code>
0592: * at which the data is written.
0593: * @param len the maximum number of bytes to read.
0594: * @return the total number of bytes read into the buffer, or
0595: * <code>-1</code> if there is no more data because the end of
0596: * the stream has been reached.
0597: * @exception IOException if an I/O error occurs.
0598: */
0599: private int readFromBuffer(byte b[], int off, int len)
0600: throws IOException {
0601:
0602: /*
0603: * copy http buffer data into user buffer, then
0604: * increment and decrement counters
0605: */
0606:
0607: int rc;
0608:
0609: if (len > bytesleft) {
0610: rc = bytesleft;
0611: } else {
0612: rc = len;
0613: }
0614:
0615: System.arraycopy(readbuf, bytesread, b, off, rc);
0616:
0617: bytesleft -= rc;
0618: bytesread += rc;
0619:
0620: return rc;
0621: }
0622:
0623: /**
0624: * Returns the number of bytes that can be read (or skipped over) from
0625: * this input stream without blocking by the next caller of a method for
0626: * this input stream. The next caller might be the same thread or
0627: * another thread.
0628: *
0629: * @return the number of bytes that can be read from this input stream
0630: * without blocking.
0631: * @exception IOException if an I/O error occurs.
0632: */
0633: public int available() throws IOException {
0634: int bytesAvailable;
0635:
0636: /*
0637: * Only after all the headers have been processed can
0638: * an accurate available count be provided.
0639: */
0640: if (!requestFinished || eof) {
0641: return 0;
0642: }
0643:
0644: /*
0645: * Regardless of chunked or non-chunked transfers -
0646: * if data is already buffered return the amount
0647: * buffered.
0648: */
0649: if (bytesleft > 0) {
0650: return bytesleft;
0651: }
0652:
0653: if (chunkedIn && totalbytesread == chunksize) {
0654: /*
0655: * Check if a new chunk size header is available.
0656: */
0657: return readChunkSizeNonBlocking();
0658: }
0659:
0660: /*
0661: * Otherwise rely on the lower level stream available
0662: * count for the nonchunked input stream.
0663: */
0664: bytesAvailable = streamInput.available();
0665: if (chunksize <= bytesAvailable) {
0666: return chunksize;
0667: }
0668:
0669: return bytesAvailable;
0670: }
0671:
0672: /**
0673: * Read a chunk size header into the readLine buffer
0674: * without blocking. The stringbuffer is populated
0675: * with characters one at a time. This routine is design
0676: * so that a partial chunk size header could be read
0677: * and then completed by a blocking read of the chunk
0678: * or a subsequent call to available.
0679: *
0680: * @return available data that can be read
0681: */
0682: int readChunkSizeNonBlocking() throws IOException {
0683: /*
0684: * Check the underlying stream to see how many bytes are
0685: * available. Do not read beyond the available characters,
0686: * because that would block.
0687: */
0688: int len = streamInput.available();
0689:
0690: /* Reset the last character from the current readLine buffer. */
0691: int sblen = stringbuffer.length();
0692: char lastchar = '\0';
0693: if (sblen > 0) {
0694: lastchar = stringbuffer.charAt(sblen - 1);
0695: }
0696:
0697: int size = -1;
0698: /*
0699: * Loop through the available characters until a full
0700: * chunk size header is in the readLine buffer.
0701: */
0702: for (; len > 0; len--) {
0703: char c = (char) streamInput.read();
0704:
0705: if (lastchar == '\r' && c == '\n') {
0706: // remove the '\r' from the buffer
0707: stringbuffer.setLength(stringbuffer.length() - 1);
0708:
0709: if (stringbuffer.length() > 0) {
0710: // this is a size, not the CRLF at the end of a chunk
0711: try {
0712: String temp = stringbuffer.toString();
0713: int semi = temp.indexOf(';');
0714:
0715: // skip extensions
0716: if (semi > 0) {
0717: temp = temp.substring(0, semi);
0718: }
0719:
0720: /*
0721: * Reset the string buffer length so readline() will
0722: * not parse this line.
0723: */
0724: stringbuffer.setLength(0);
0725:
0726: size = Integer.parseInt(temp, 16);
0727: } catch (NumberFormatException nfe) {
0728: throw new IOException(
0729: "invalid chunk size number format");
0730: }
0731: break;
0732: }
0733: } else {
0734: stringbuffer.append(c);
0735: lastchar = c;
0736: }
0737: }
0738:
0739: if (size < 0) {
0740: // did not get the size
0741: return 0;
0742: }
0743:
0744: /*
0745: * Update the chunksize and the total bytes that have been
0746: * read from the chunk. This will trigger the next call to
0747: * readBytes to refill the buffers as needed.
0748: */
0749: chunksize = size;
0750: if (size == 0) {
0751: eof = true;
0752: return 0;
0753: }
0754:
0755: totalbytesread = 0;
0756:
0757: /*
0758: * If the full chunk is available, return chunksize,
0759: * otherwise return the remainder of the available
0760: * bytes (e.g. partial chunk).
0761: */
0762: return (chunksize < len ? chunksize : len);
0763:
0764: }
0765:
0766: /**
0767: * Reads up to <code>len</code> bytes of data from the input stream into
0768: * an array of bytes.
0769: * This method reads Chunked and known length non-chunked http connection
0770: * input streams. For non-chunked set the field <code>chunkedIn</code>
0771: * should be false.
0772: *
0773: * @param b the buffer into which the data is read.
0774: * @param off the start offset in array <code>b</code>
0775: * at which the data is written.
0776: * @param len the maximum number of bytes to read.
0777: * @return the total number of bytes read into the buffer, or
0778: * <code>-1</code> if there is no more data because the end of
0779: * the stream has been reached.
0780: * @exception IOException if an I/O error occurs.
0781: */
0782: protected int readBytesChunked(byte b[], int off, int len)
0783: throws IOException {
0784:
0785: int rc;
0786:
0787: if (bytesleft == 0) {
0788: /*
0789: * the internal input stream buffer is empty, read from the stream
0790: */
0791:
0792: if (totalbytesread == chunksize) {
0793: /*
0794: * read the end of the chunk and get the size of the
0795: * the next if there is one
0796: */
0797:
0798: if (!chunkedIn) {
0799: /*
0800: * non-chucked data is treated as one big chunk so there
0801: * is no more data so just return as if there are no
0802: * more chunks
0803: */
0804: eof = true;
0805: return -1;
0806: }
0807:
0808: skipEndOfChunkCRLF();
0809:
0810: chunksize = readChunkSize();
0811: if (chunksize == 0) {
0812: eof = true;
0813:
0814: /*
0815: * REFERENCE: HTTP1.1 document
0816: * SECTION: 3.6.1 Chunked Transfer Coding
0817: * in some cases there may be an OPTIONAL trailer
0818: * containing entity-header fields. since we don't support
0819: * the available() method for TCP socket input streams and
0820: * for performance and reuse reasons we do not attempt to
0821: * clean up the current connections input stream.
0822: * check readResponseMessage() method in this class for
0823: * more details
0824: */
0825: return -1;
0826: }
0827:
0828: /*
0829: * we have not read any bytes from this new chunk
0830: */
0831: totalbytesread = 0;
0832: }
0833:
0834: int bytesToRead = chunksize - totalbytesread;
0835:
0836: if (len >= bytesToRead) {
0837:
0838: /*
0839: * No need to buffer, if the caller has given a big buffer.
0840: */
0841: rc = streamInput.read(b, off, bytesToRead);
0842:
0843: } else if (len >= inputBufferSize) {
0844: /*
0845: * No need to buffer, if the caller has given a big buffer.
0846: */
0847: rc = streamInput.read(b, off, len);
0848: } else {
0849: if (inputBufferSize >= bytesToRead) {
0850: rc = streamInput.read(readbuf, 0, bytesToRead);
0851: } else {
0852: rc = streamInput.read(readbuf, 0, inputBufferSize);
0853: }
0854:
0855: bytesleft = rc;
0856: bytesread = 0;
0857: }
0858:
0859: if (rc == -1) {
0860: /*
0861: * Network problem or the wrong length was sent by the server.
0862: */
0863: eof = true;
0864: throw new IOException("unexpected end of stream");
0865: }
0866:
0867: totalbytesread += rc;
0868:
0869: if (bytesleft == 0) {
0870: /*
0871: * The data was read directly into the caller's buffer.
0872: */
0873: return rc;
0874: }
0875: }
0876:
0877: rc = readFromBuffer(b, off, len);
0878:
0879: return rc;
0880: }
0881:
0882: /**
0883: * Read the chunk size from the input.
0884: * It is a hex length followed by optional headers (ignored).
0885: * and terminated with CRLF.
0886: *
0887: * @return size of the buffered read
0888: */
0889: private int readChunkSize() throws IOException {
0890: int size = -1;
0891:
0892: try {
0893: String chunk = null;
0894:
0895: try {
0896: chunk = readLine(streamInput);
0897: } catch (IOException ioe) {
0898: /* throw new IOException(ioe.getMessage()); */
0899: }
0900:
0901: if (chunk == null) {
0902: throw new IOException("No Chunk Size");
0903: }
0904:
0905: int i;
0906: for (i = 0; i < chunk.length(); i++) {
0907: char ch = chunk.charAt(i);
0908: if (Character.digit(ch, 16) == -1)
0909: break;
0910: }
0911:
0912: /* look at extensions?.... */
0913: size = Integer.parseInt(chunk.substring(0, i), 16);
0914: } catch (NumberFormatException e) {
0915: throw new IOException("invalid chunk size number format");
0916: }
0917:
0918: return size;
0919: }
0920:
0921: /**
0922: * Skips the CRLF at the end of each chunk in the InputStream.
0923: *
0924: * @exception IOException if the LF half of the ending CRLF
0925: * is missing.
0926: */
0927: private void skipEndOfChunkCRLF() throws IOException {
0928: int ch;
0929:
0930: if (stringbuffer.length() > 1) {
0931: /*
0932: * readChunkSizeNonBlocking does not leave CRLF in the buffer
0933: * so assume that the ending CRLF has been skipped already
0934: */
0935: return;
0936: }
0937:
0938: // readChunkSizeNonBlocking could have left a \r single in the buffer
0939: if (stringbuffer.length() == 1) {
0940: if (stringbuffer.charAt(0) != '\r') {
0941: // assume that the ending CRLF has been skipped already
0942: return;
0943: }
0944:
0945: // remove the '\r'
0946: stringbuffer.setLength(0);
0947: ch = streamInput.read();
0948: if (ch != '\n') {
0949: throw new IOException(
0950: "missing the LF of an expected CRLF");
0951: }
0952:
0953: return;
0954: }
0955:
0956: ch = streamInput.read();
0957: if (ch != '\r') {
0958: /*
0959: * assume readChunkSizeNonBlocking has read the end of the chunk
0960: * and that this is the next chunk size, so put the char in the
0961: * buffer for readChunkSize and return
0962: */
0963: stringbuffer.append(ch);
0964: return;
0965: }
0966:
0967: ch = streamInput.read();
0968: if (ch != '\n') {
0969: throw new IOException("missing the LF of an expected CRLF");
0970: }
0971: }
0972:
0973: /**
0974: * Writes <code>len</code> bytes from the specified byte array
0975: * starting at offset <code>off</code> to this output stream.
0976: *
0977: * <p>
0978: * This method can only be called after an OutputStream setup has be
0979: * done.
0980: *
0981: * @param b the data.
0982: * @param off the start offset in the data.
0983: * @param len the number of bytes to write.
0984: *
0985: * @return int number of bytes written to stream
0986: *
0987: * @exception IOException if an I/O error occurs. In particular,
0988: * an <code>IOException</code> is thrown if the output
0989: * stream is closed.
0990: *
0991: * @exception IllegalStateException if an attempt was made to
0992: * write after the request has finished.
0993: */
0994: protected int writeBytes(byte b[], int off, int len)
0995: throws IOException {
0996:
0997: int bytesToCopy;
0998:
0999: if (requestFinished) {
1000: throw new IllegalStateException(
1001: "Write attempted after request finished");
1002: }
1003:
1004: if (bytesToWrite == outputDataSize) {
1005: /*
1006: * Send the bytes in the write buffer as a chunk to the server
1007: * so more bytes can be put in the buffer.
1008: */
1009: sendRequest(true, false);
1010: }
1011:
1012: /*
1013: * Our parent class will call this method in a loop until all the bytes
1014: * are written. So this method does not have to process all the bytes
1015: * in one call.
1016: */
1017: bytesToCopy = outputDataSize - bytesToWrite;
1018: if (len < bytesToCopy) {
1019: bytesToCopy = len;
1020: }
1021:
1022: System.arraycopy(b, off, writebuf, HTTP_OUTPUT_DATA_OFFSET
1023: + bytesToWrite, bytesToCopy);
1024: bytesToWrite += bytesToCopy;
1025:
1026: return bytesToCopy;
1027: }
1028:
1029: /**
1030: * If any output data, turn on chunking send it to the server.
1031: *
1032: * @exception IOException if an I/O error occurs
1033: *
1034: * @exception IllegalStateException if there was an attempt was made to
1035: * flush after the request has finished.
1036: */
1037: public void flush() throws IOException {
1038:
1039: if (bytesToWrite <= 0) {
1040: return;
1041: }
1042:
1043: if (requestFinished) {
1044: throw new IllegalStateException(
1045: "Flush attempted after request finished");
1046: }
1047:
1048: sendRequest(true, false);
1049: }
1050:
1051: /**
1052: * Get the original URL used to open the HTTP connection.
1053: *
1054: * @return HTTP URL used in the current connection
1055: */
1056: public String getURL() {
1057: return url.toString();
1058: }
1059:
1060: /**
1061: * Get the protocol scheme parsed from the URL.
1062: *
1063: * @return protocol scheme is "http"
1064: */
1065: public String getProtocol() {
1066: return protocol;
1067: }
1068:
1069: /**
1070: * Get the host name parsed from the URL.
1071: *
1072: * @return host name from the parsed URL
1073: */
1074: public String getHost() {
1075: return url.host;
1076: }
1077:
1078: /**
1079: * Get the file path name parsed from the URL.
1080: *
1081: * @return file path name from the parsed URL
1082: */
1083: public String getFile() {
1084: return url.path;
1085: }
1086:
1087: /**
1088: * Get the fragment identifier parsed from the URL.
1089: *
1090: * @return reference component from the parsed URL
1091: */
1092: public String getRef() {
1093: return url.fragment;
1094: }
1095:
1096: /**
1097: * Get the query string parsed from the URL.
1098: *
1099: * @return query string from the parsed URL
1100: */
1101: public String getQuery() {
1102: return url.query;
1103: }
1104:
1105: /**
1106: * Get the query string parsed from the URL.
1107: *
1108: * @return query string from the parsed URL
1109: */
1110: public int getPort() {
1111: return url.port;
1112: }
1113:
1114: /**
1115: * Get the request method of the current connection.
1116: *
1117: * @return request method is GET, HEAD or POST
1118: * @see #setRequestMethod
1119: */
1120: public String getRequestMethod() {
1121: return method;
1122: }
1123:
1124: /**
1125: * Set the request method of the current connection.
1126: *
1127: * @param method request method is GET, HEAD or POST
1128: * @exception IOException is thrown if the connection is already open
1129: * @see #getRequestMethod
1130: */
1131: public void setRequestMethod(String method) throws IOException {
1132: ensureOpen();
1133:
1134: if (streamConnection != null) {
1135: throw new IOException("connection already open");
1136: }
1137:
1138: /*
1139: * The request method can not be changed once the output
1140: * stream has been opened.
1141: */
1142: if (maxOStreams == 0) {
1143: return;
1144: }
1145:
1146: if (!method.equals(HEAD) && !method.equals(GET)
1147: && !method.equals(POST)) {
1148: throw new IOException("unsupported method: " + method);
1149: }
1150:
1151: this .method = method;
1152: }
1153:
1154: /**
1155: * Get the request header value for the named property.
1156: *
1157: * @param key property name of specific HTTP 1.1 header field
1158: * @return value of the named property, if found, null otherwise.
1159: * @see #setRequestProperty
1160: */
1161: public String getRequestProperty(String key) {
1162:
1163: /* https handles the proxy fields in a different way */
1164: if (key.toLowerCase().startsWith("proxy-")) {
1165: return proxyHeaders.getPropertyIgnoreCase(key);
1166: }
1167:
1168: return reqProperties.getPropertyIgnoreCase(key);
1169: }
1170:
1171: /**
1172: * Set the request header name/value of specific HTTP 1.1 header field.
1173: *
1174: * @param key property name
1175: * @param value property value
1176: * @exception IllegalArgumentException is thrown if value contains CRLF.
1177: * @exception IOException If some other kind of I/O error occurs.
1178: * @see #getRequestProperty
1179: */
1180: public void setRequestProperty(String key, String value)
1181: throws IOException {
1182: int index = 0;
1183:
1184: ensureOpen();
1185:
1186: if (streamConnection != null) {
1187: throw new IOException("connection already open");
1188: }
1189:
1190: /*
1191: * The request headers can not be changed once the output
1192: * stream has been opened.
1193: */
1194: if (maxOStreams == 0) {
1195: return;
1196: }
1197:
1198: // Look to see if a programmer embedded any extra fields.
1199: for (;;) {
1200: index = value.indexOf("\r\n", index);
1201:
1202: if (index == -1) {
1203: break;
1204: }
1205:
1206: // Allow legal header value continuations. CRLF + (SP|HT)
1207: index += 2;
1208:
1209: if (index >= value.length()
1210: || (value.charAt(index) != ' ' && value
1211: .charAt(index) != '\t')) {
1212: // illegal values passed for properties - raise an exception
1213: throw new IllegalArgumentException(
1214: "illegal value found");
1215: }
1216: }
1217:
1218: setRequestField(key, value);
1219: }
1220:
1221: /**
1222: * Add the named field to the list of request fields.
1223: * This method is where a subclass should override properties.
1224: *
1225: * @param key key for the request header field.
1226: * @param value the value for the request header field.
1227: */
1228: protected void setRequestField(String key, String value) {
1229:
1230: /* https handles the proxy fields in a different way */
1231: if (key.toLowerCase().startsWith("proxy-")) {
1232: proxyHeaders.setPropertyIgnoreCase(key, value);
1233: return;
1234: }
1235:
1236: /*
1237: * If application setRequestProperties("Connection", "close")
1238: * then we need to know this & take appropriate default close action
1239: */
1240: if ((key.equalsIgnoreCase("connection"))
1241: && (value.equalsIgnoreCase("close"))) {
1242: ConnectionCloseFlag = true;
1243: }
1244:
1245: /*
1246: * Ref . Section 3.6 of RFC2616 :
1247: * All transfer-coding values are case-insensitive.
1248: */
1249: if ((key.equalsIgnoreCase("transfer-encoding"))
1250: && (value.equalsIgnoreCase("chunked"))) {
1251: chunkedOut = true;
1252: }
1253:
1254: reqProperties.setPropertyIgnoreCase(key, value);
1255: }
1256:
1257: /**
1258: * Get the response code of the current request.
1259: *
1260: * @return numeric value of the parsed response code
1261: * @exception IOException is thrown if a network error occurs
1262: */
1263: public int getResponseCode() throws IOException {
1264: ensureOpen();
1265:
1266: sendRequest();
1267:
1268: return responseCode;
1269: }
1270:
1271: /**
1272: * Get the response message of the current request.
1273: *
1274: * @return message associated with the current response header
1275: * @exception IOException is thrown if a network error occurs
1276: */
1277: public String getResponseMessage() throws IOException {
1278: ensureOpen();
1279:
1280: sendRequest();
1281:
1282: return responseMsg;
1283: }
1284:
1285: /**
1286: * Get the Content-Length for the current response.
1287: *
1288: * @return length of data to be transmitted after the response headers
1289: */
1290: public long getLength() {
1291: try {
1292: ensureOpen();
1293:
1294: sendRequest();
1295: } catch (IOException ioe) {
1296: // Fall through to return -1 for length
1297: }
1298:
1299: return contentLength;
1300:
1301: }
1302:
1303: /**
1304: * Get the Content-Type for the current response.
1305: *
1306: * @return MIME type of data to be transmitted after the response header
1307: */
1308: public String getType() {
1309: try {
1310: return getHeaderField("content-type");
1311: } catch (IOException x) {
1312: return null;
1313: }
1314:
1315: }
1316:
1317: /**
1318: * Get the Content-Encoding for the current response.
1319: *
1320: * @return encoding type of data to be transmitted after the
1321: * response headers
1322: */
1323: public String getEncoding() {
1324: try {
1325: return getHeaderField("content-encoding");
1326: } catch (IOException x) {
1327: return null;
1328: }
1329:
1330: }
1331:
1332: /**
1333: * Get the Expires header for the current response.
1334: *
1335: * @return expiration data for the transmitted data
1336: *
1337: * @exception IOException is thrown if a network error occurs
1338: */
1339: public long getExpiration() throws IOException {
1340: return getHeaderFieldDate("expires", 0);
1341: }
1342:
1343: /**
1344: * Get the Date header for the current response.
1345: *
1346: * @return timestamp for the data transmission event
1347: *
1348: * @exception IOException is thrown if a network error occurs
1349: */
1350: public long getDate() throws IOException {
1351: return getHeaderFieldDate("date", 0);
1352: }
1353:
1354: /**
1355: * Get the Last-Modified date header for the current response.
1356: *
1357: * @return timestamp for the transmitted data last modification
1358: *
1359: * @exception IOException is thrown if a network error occurs
1360: */
1361: public long getLastModified() throws IOException {
1362: return getHeaderFieldDate("last-modified", 0);
1363: }
1364:
1365: /**
1366: * Get the named header field for the current response.
1367: *
1368: * @param name header field to be examined
1369: * @return value of requested header, if found, otherwise null
1370: *
1371: * @exception IOException is thrown if a network error occurs
1372: */
1373: public String getHeaderField(String name) throws IOException {
1374: ensureOpen();
1375:
1376: sendRequest();
1377:
1378: return (headerFields.getPropertyIgnoreCase(name));
1379:
1380: }
1381:
1382: /**
1383: * Get the indexed header field for the current response.
1384: *
1385: * @param index header field offset to be examined
1386: * @return key name of requested header, if found, otherwise null
1387: *
1388: * @exception IOException is thrown if a network error occurs
1389: */
1390: public String getHeaderField(int index) throws IOException {
1391: ensureOpen();
1392:
1393: sendRequest();
1394:
1395: if (index >= headerFields.size()) {
1396: return null;
1397: }
1398:
1399: return (headerFields.getValueAt(index));
1400: }
1401:
1402: /**
1403: * Get the indexed header field value for the current response.
1404: *
1405: * @param index header field value offset to be examined
1406: * @return value of requested header, if found, otherwise null
1407: *
1408: * @exception IOException is thrown if a network error occurs
1409: */
1410: public String getHeaderFieldKey(int index) throws IOException {
1411: ensureOpen();
1412:
1413: sendRequest();
1414:
1415: if (index >= headerFields.size())
1416: return null;
1417:
1418: return ((String) (headerFields.getKeyAt(index)));
1419: }
1420:
1421: /**
1422: * Get the named header field for the current response and return a
1423: * numeric value for the parsed field, with a supplied default value
1424: * if the field does not exist or can not be parsed cleanly.
1425: *
1426: * @param name of the field to be examined
1427: * @param def default value to use, if field is not parsable
1428: * @return numeric value of requested header, if found, otherwise
1429: * supplied default is returned
1430: *
1431: * @exception IOException is thrown if a network error occurs
1432: */
1433: public int getHeaderFieldInt(String name, int def)
1434: throws IOException {
1435: ensureOpen();
1436:
1437: sendRequest();
1438:
1439: try {
1440: return Integer.parseInt(getHeaderField(name));
1441: } catch (IllegalArgumentException iae) {
1442: // fall through
1443: } catch (NullPointerException npe) {
1444: // fall through
1445: }
1446:
1447: return def;
1448: }
1449:
1450: /**
1451: * Get the named header field for the current response and return a date
1452: * value for the parsed field,with a supplied default value if the field
1453: * does not exist or can not be parsed cleanly.
1454: *
1455: * @param name of the field to be examined
1456: * @param def default value to use, if field is not parsable
1457: * @return date value of requested header, if found, otherwise
1458: * supplied default is returned
1459: *
1460: * @exception IOException is thrown if a network error occurs
1461: */
1462: public long getHeaderFieldDate(String name, long def)
1463: throws IOException {
1464: ensureOpen();
1465:
1466: sendRequest();
1467:
1468: try {
1469: return DateParser.parse(getHeaderField(name));
1470: } catch (NumberFormatException nfe) {
1471: // fall through
1472: } catch (IllegalArgumentException iae) {
1473: // fall through
1474: } catch (NullPointerException npe) {
1475: // fall through
1476: }
1477:
1478: return def;
1479: }
1480:
1481: /**
1482: * If not connected, connect to the underlying socket transport
1483: * and send the HTTP request and get the response header.
1484: * <P>
1485: * If an http_proxy was specified the socket connection will be made to
1486: * the proxy server and the requested URL will include the full http URL.
1487: * <P>
1488: * On output the Content-Length header is included in the request based
1489: * on the size of the buffered output array.
1490: * <P>
1491: * This routine inserts the Host header needed for HTTP 1.1 virtual host
1492: * addressing.
1493: * <P>
1494: * This routine also receives the reply response and parses the headers
1495: * for easier access. After the headers are parsed the application has
1496: * access to the raw data from the socket stream connection.
1497: *
1498: * @exception IOException is thrown if the connection cannot be opened
1499: */
1500: protected void sendRequest() throws IOException {
1501: sendRequest(false, true);
1502: }
1503:
1504: /**
1505: * If not connected, connect to the underlying socket transport
1506: * and send the HTTP request and get the response header.
1507: * <P>
1508: * If an http_proxy was specified the socket connection will be made to
1509: * the proxy server and the requested URL will include the full http URL.
1510: * <P>
1511: * On output the Content-Length header is included in the request based
1512: * on the size of the buffered output array.
1513: * <P>
1514: * This routine inserts the Host header needed for HTTP 1.1 virtual host
1515: * addressing.
1516: * <P>
1517: * This routine also receives the reply response and parses the headers
1518: * for easier access. After the headers are parsed the application has
1519: * access to the raw data from the socket stream connection.
1520: *
1521: * @param chunkData if true chunk data sent to the server
1522: * @param readResponseHeader if true, read the response header
1523: *
1524: * @exception IOException is thrown if the connection cannot be opened
1525: */
1526: private void sendRequest(boolean chunkData,
1527: boolean readResponseHeader) throws IOException {
1528: int bytesToRetry;
1529:
1530: if (sendingRequest || requestFinished) {
1531: return;
1532: }
1533:
1534: sendingRequest = true;
1535:
1536: try {
1537: if (chunkData) {
1538: chunkedOut = true;
1539: }
1540:
1541: bytesToRetry = bytesToWrite;
1542:
1543: try {
1544: startRequest();
1545: sendRequestBody();
1546:
1547: if (readResponseHeader) {
1548: finishRequestGetResponseHeader();
1549: }
1550: } catch (IOException ioe) {
1551: if (!(streamConnection instanceof StreamConnectionElement)) {
1552: /*
1553: * This was a connection opened during this transaction.
1554: * So do not try to recover.
1555: */
1556: throw ioe;
1557: }
1558:
1559: try {
1560: connectionPool
1561: .remove((StreamConnectionElement) streamConnection);
1562: } catch (Exception e) {
1563: // do not over throw the previous exception
1564: }
1565:
1566: if (firstChunkSent) {
1567: // can't retry since we do not have the previous chunk
1568: throw new IOException(
1569: "Persistent connection dropped "
1570: + "after first chunk sent, cannot retry");
1571: }
1572:
1573: streamConnection = null;
1574: streamInput = null;
1575: streamOutput = null;
1576: bytesToWrite = bytesToRetry;
1577:
1578: startRequest();
1579: sendRequestBody();
1580:
1581: if (readResponseHeader) {
1582: finishRequestGetResponseHeader();
1583: }
1584: }
1585:
1586: if (chunkedOut) {
1587: firstChunkSent = true;
1588: }
1589: } finally {
1590: sendingRequest = false;
1591: }
1592: }
1593:
1594: /**
1595: * If not connected, connect to the underlying socket transport
1596: * and send the HTTP request headers.
1597: * <P>
1598: * If an http_proxy was specified the socket connection will be made to
1599: * the proxy server and the requested URL will include the full http URL.
1600: * <P>
1601: * On output the Content-Length header is included in the request based
1602: * on the size of the buffered output array.
1603: * <P>
1604: * This routine inserts the Host header needed for HTTP 1.1 virtual host
1605: * addressing.
1606: * <P>
1607: * This routine also receives the reply response and parses the headers
1608: * for easier access. After the headers are parsed the application has
1609: * access to the raw data from the socket stream connection.
1610: *
1611: * @exception IOException is thrown if the connection cannot be opened
1612: */
1613: void startRequest() throws IOException {
1614: if (streamConnection != null) {
1615: return;
1616: }
1617:
1618: streamConnect();
1619: sendRequestHeader();
1620: }
1621:
1622: /**
1623: * Find a previous connection in the pool or try to connect to the
1624: * underlying stream transport.
1625: *
1626: * @exception IOException is thrown if the connection cannot be opened
1627: */
1628: private void streamConnect() throws IOException {
1629: streamConnection = connect();
1630:
1631: /*
1632: * Because StreamConnection.open*Stream cannot be called twice
1633: * the HTTP connect method may have already open the streams
1634: * to connect to the proxy and saved them in the field variables
1635: * already.
1636: */
1637: if (streamOutput != null) {
1638: return;
1639: }
1640:
1641: streamOutput = streamConnection.openDataOutputStream();
1642: streamInput = streamConnection.openDataInputStream();
1643: }
1644:
1645: /**
1646: * Gets the underlying stream connection.
1647: *
1648: * @return underlying stream connection
1649: */
1650: protected StreamConnection getStreamConnection() {
1651: return streamConnection;
1652: }
1653:
1654: /**
1655: * Simplifies the sendRequest() method header functionality into one method
1656: * this is extremely helpful for persistent connection support and
1657: * retries.
1658: *
1659: * @exception IOException is thrown if the connection cannot be opened
1660: */
1661: private void sendRequestHeader() throws IOException {
1662: StringBuffer reqLine;
1663: String filename;
1664: int numberOfKeys;
1665:
1666: /*
1667: * JTWI security policy for untrusted MIDlets says to add a
1668: * user-agent field with the value "UNTRUSTED/1.0" but still include
1669: * the applications value. The TCP and SSL protocols will deny the
1670: * standard ports to untrusted applications so they cannot get around
1671: * this field being added to their HTTP(S) requests.
1672: */
1673: if (!ownerTrusted) {
1674: String newUserAgentValue;
1675: String origUserAgentValue = reqProperties
1676: .getPropertyIgnoreCase("User-Agent");
1677: if (origUserAgentValue != null) {
1678: /*
1679: * HTTP header values can be concatenated, so original value
1680: * of the "User-Agent" header field should not be ignored in
1681: * this case
1682: */
1683: newUserAgentValue = "UNTRUSTED/1.0 "
1684: + origUserAgentValue;
1685: } else {
1686: newUserAgentValue = "UNTRUSTED/1.0";
1687: }
1688: reqProperties.setPropertyIgnoreCase("User-Agent",
1689: newUserAgentValue);
1690: }
1691:
1692: // HTTP 1.0 requests must contain content length for proxies
1693: if (getRequestProperty("Content-Length") == null) {
1694: setRequestField("Content-Length", Integer
1695: .toString(bytesToWrite));
1696: }
1697:
1698: reqLine = new StringBuffer(256);
1699:
1700: /*
1701: * HTTP RFC and CR#4402149,
1702: * if there is no path then add a slash ("/").
1703: */
1704: filename = url.path;
1705: if (filename == null) {
1706: filename = "/";
1707: }
1708:
1709: /*
1710: * Note: the "ref" or fragment, is not sent to the server.
1711: */
1712: reqLine.append(method);
1713: reqLine.append(" ");
1714:
1715: /*
1716: * Since we now use a tunnel instead of a proxy, we do not have
1717: * to send an absolute URI. The difference is that a proxy will
1718: * strip scheme and authority from the URI and a tunnel will not.
1719: *
1720: * For HTTPS purposes we will use the relative URI.
1721: *
1722: * Some HTTPS server's do not like to see "https" as the scheme of an
1723: * URI and only recognize "http".
1724: * examples: www.wellsfargo.com sends back html with not HTTP headers,
1725: * e-banking.abbeynational.co.uk sends back a 404 to all requests.
1726: *
1727: * It is better to not use the absolute URL, than to hardcode the
1728: * the scheme to "http" all the time since that did not work with
1729: * e-banking.abbeynational.co.uk.
1730: *
1731: * if (http_proxy != null) {
1732: * reqLine.append(protocol);
1733: * reqLine.append("://");
1734: * reqLine.append(url.authority);
1735: * }
1736: */
1737:
1738: reqLine.append(filename);
1739:
1740: if (url.query != null) {
1741: reqLine.append("?");
1742: reqLine.append(url.query);
1743: }
1744:
1745: reqLine.append(" ");
1746: reqLine.append(HTTP_VERSION);
1747: reqLine.append("\r\n");
1748:
1749: /*
1750: * HTTP 1/1 requests require the Host header to distinguish
1751: * virtual host locations.
1752: */
1753:
1754: setRequestField("Host", url.authority);
1755:
1756: if (chunkedOut) {
1757: /*
1758: * Signal the server that the body is chunked
1759: * by setting the Transfer-Encoding property to "chunked".
1760: */
1761: setRequestField("Transfer-Encoding", "chunked");
1762: }
1763:
1764: /*
1765: * Setup the various http header field/values defined and/or
1766: * required.
1767: */
1768: numberOfKeys = reqProperties.size();
1769: for (int i = 0; i < numberOfKeys; i++) {
1770: String key = (String) reqProperties.getKeyAt(i);
1771:
1772: if (key.equals("Content-Length")) {
1773: /*
1774: * If its CHUNK data - no content-length: size required.
1775: */
1776: if (chunkedOut) {
1777: continue;
1778: } else {
1779: /*
1780: * Check that the output stream has been opened.
1781: */
1782: if (writebuf == null) {
1783: reqLine.append("Content-Length: 0");
1784: } else {
1785: reqLine.append("Content-Length: ");
1786: reqLine.append(bytesToWrite);
1787: }
1788:
1789: reqLine.append("\r\n");
1790: }
1791: } else {
1792: reqLine.append(key);
1793: reqLine.append(": ");
1794: reqLine.append(reqProperties.getValueAt(i));
1795: reqLine.append("\r\n");
1796: }
1797: }
1798:
1799: reqLine.append("\r\n");
1800:
1801: streamOutput.write(reqLine.toString().getBytes());
1802: }
1803:
1804: /**
1805: * Write the http request body bytes to the output stream.
1806: *
1807: * @exception IOException
1808: */
1809: protected void sendRequestBody() throws IOException {
1810:
1811: int start;
1812: int endOfData;
1813: int length;
1814:
1815: if ((writebuf == null) || (bytesToWrite == 0)) {
1816: return;
1817: }
1818:
1819: start = HTTP_OUTPUT_DATA_OFFSET;
1820: endOfData = HTTP_OUTPUT_DATA_OFFSET + bytesToWrite;
1821: length = bytesToWrite;
1822:
1823: /*
1824: * If a CHUNKed session then write out the chunk size first
1825: * with a trailing CRLF.
1826: *
1827: * reference: RFC2616 - section 3 protocol parameters
1828: * 3.6 transfer coding:
1829: * 3.6.1 chunked transfer coding:
1830: * chunk-body = chunk / last chunk / trailer / CRLF
1831: * * chunk = chunk-size / chunk-data / CRLF
1832: * last_chunk = "0" / CRLF
1833: * trailer = " " / CRLF
1834: * *indicates its done here.
1835: */
1836: if (chunkedOut) {
1837: /*
1838: * For CHUNKed write out the chunk size with CRLF.
1839: * Put this before the data in write buffer.
1840: */
1841: String temp = Integer.toHexString(bytesToWrite);
1842: int tempLen = temp.length();
1843:
1844: writebuf[--start] = (byte) '\n';
1845: writebuf[--start] = (byte) '\r';
1846: for (int i = tempLen - 1; i >= 0; i--) {
1847: writebuf[--start] = (byte) temp.charAt(i);
1848: }
1849:
1850: length += tempLen + 2;
1851:
1852: /*
1853: * If a CHUNKed session then write out another CRLF and flush().
1854: * Put this after the data in the write buffer.
1855: */
1856: writebuf[endOfData++] = (byte) '\r';
1857: writebuf[endOfData++] = (byte) '\n';
1858: length += 2;
1859: }
1860:
1861: streamOutput.write(writebuf, start, length);
1862: bytesToWrite = 0;
1863: }
1864:
1865: /**
1866: * Finish the http request and reads the response headers.
1867: *
1868: * @exception IOException is thrown, if an I/O error occurs for final
1869: * stream output or on reading the response message line
1870: */
1871: protected void finishRequestGetResponseHeader() throws IOException {
1872:
1873: // Even if we get an exception this request is finished
1874: requestFinished = true;
1875:
1876: /*
1877: * if this is a CHUNKed session write out the last set of CRLF
1878: */
1879: if (chunkedOut) {
1880: /*
1881: * reference: RFC2616 - section 3 protocol parameters
1882: * 3.6 transfer coding:
1883: * 3.6.1 chunked transfer coding:
1884: * chunk-body = chunk / last chunk / trailer / CRLF
1885: * chunk = chunk-size / chunk-data / CRLF
1886: * * last_chunk = "0" / CRLF
1887: * * trailer = " " / CRLF
1888: * * indicates its done here.
1889: */
1890:
1891: /*
1892: * write the last chunk (size=0 / CRLF) and the dummy trailer
1893: */
1894: streamOutput.write("0\r\n\r\n".getBytes());
1895: }
1896:
1897: streamOutput.flush();
1898:
1899: readResponseMessage(streamInput);
1900:
1901: readHeaders(streamInput);
1902:
1903: /*
1904: * Ignore a continuation header and read the true headers again.
1905: * (CR# 4382226 discovered with Jetty HTTP 1.1 web server.
1906: */
1907: if (responseCode == 100) {
1908: readResponseMessage(streamInput);
1909: readHeaders(streamInput);
1910: }
1911: }
1912:
1913: /**
1914: * Connect to the underlying network TCP transport.
1915: * If the proxy is configured, connect to it as tunnel first.
1916: * <p>
1917: * Warning: A subclass that implements this method, should not call this
1918: * method and should implement the disconnect method.
1919: *
1920: * @return network stream connection
1921: * @exception IOException is thrown if the connection cannot be opened
1922: */
1923: protected StreamConnection connect() throws IOException {
1924: StreamConnection sc;
1925: com.sun.midp.io.j2me.socket.Protocol conn;
1926:
1927: if (!permissionChecked) {
1928: throw new SecurityException();
1929: }
1930:
1931: sc = connectionPool.get(classSecurityToken, protocol, url.host,
1932: url.port);
1933:
1934: if (sc != null) {
1935: return sc;
1936: }
1937:
1938: conn = new com.sun.midp.io.j2me.socket.Protocol();
1939:
1940: if (http_proxy == null) {
1941: conn.openPrim(classSecurityToken, "//" + hostAndPort);
1942:
1943: // Do not delay request since this delays the response.
1944: conn.setSocketOption(SocketConnection.DELAY, 0);
1945: return conn;
1946: }
1947:
1948: conn.openPrim(classSecurityToken, "//" + http_proxy);
1949:
1950: // Do not delay request since this delays the response.
1951: conn.setSocketOption(SocketConnection.DELAY, 0);
1952:
1953: // openData*Stream cannot be call twice, so save them for later
1954: streamOutput = conn.openDataOutputStream();
1955: streamInput = conn.openDataInputStream();
1956:
1957: try {
1958: doTunnelHandshake(streamOutput, streamInput);
1959: } catch (IOException ioe) {
1960: String response = ioe.getMessage();
1961:
1962: try {
1963: disconnect(conn, streamInput, streamOutput);
1964: } catch (Exception e) {
1965: // do not over throw the handshake exception
1966: }
1967:
1968: streamOutput = null;
1969: streamInput = null;
1970:
1971: if ((response != null) && (response.indexOf(" 500 ") > -1)) {
1972: throw new ConnectionNotFoundException(response);
1973: } else {
1974: throw ioe;
1975: }
1976: }
1977:
1978: return conn;
1979: }
1980:
1981: /**
1982: * Connects to the SSL tunnel and completes the initialization of the
1983: * tunnel (handshake). The handshake based on the Internet-Draft
1984: * "A. Luotonen, Tunneling TCP based protocols through Web proxy servers,
1985: * February 1999".
1986: * @param os output stream for secure handshake
1987: * @param is input stream for secure handshake
1988: * @exception IOException is thrown if an error occurs in the SSL handshake
1989: */
1990: protected void doTunnelHandshake(OutputStream os, InputStream is)
1991: throws IOException {
1992: String required;
1993: String optional;
1994: String endOfLine = "\r\n";
1995: String emptyLine = endOfLine;
1996: int numberOfKeys;
1997: StringBuffer temp;
1998: boolean newline;
1999: String response;
2000:
2001: /*
2002: * request = required *optional emptyLine
2003: * required = "CONNECT" SP HOST ":" PORT SP HTTP_VERSION endOfLine
2004: * optional = HTTP_HEADER endOfLine ; proxy dependent: most likely
2005: * ; used for authorization.
2006: * emptyLine = endOfLine
2007: * endOfLine = *1CR LF
2008: *
2009: * example:
2010: * CONNECT home.acme.com:443 HTTP/1.0
2011: * Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
2012: *
2013: */
2014: required = "CONNECT " + hostAndPort + " " + HTTP_VERSION
2015: + endOfLine;
2016:
2017: os.write(required.getBytes());
2018:
2019: numberOfKeys = proxyHeaders.size();
2020: for (int i = 0; i < numberOfKeys; i++) {
2021: optional = proxyHeaders.getKeyAt(i) + ": "
2022: + proxyHeaders.getValueAt(i) + endOfLine;
2023: os.write(optional.getBytes());
2024: }
2025:
2026: os.write(emptyLine.getBytes());
2027: os.flush();
2028:
2029: /*
2030: * response = status *optional emptyLine
2031: * status = HTTP_VERSION SP STATUSCODE STATUS_MESSAGE *1CR LF
2032: * optional = HTTP_HEADER *1CR LF
2033: * emptyLine = *1CR LF
2034: *
2035: * example:
2036: * HTTP/1.0 200 Connection established
2037: *
2038: */
2039:
2040: // Read in response until an empty line is found (1*CR LF 1*CR LF)
2041: temp = new StringBuffer();
2042: newline = false;
2043: while (true) {
2044: int c = is.read();
2045: if (c == -1) {
2046: break;
2047: } else if (c == '\n') {
2048: if (newline) {
2049: break;
2050: }
2051: newline = true;
2052: } else if (c != '\r') {
2053: newline = false;
2054: }
2055:
2056: temp.append((char) c);
2057: }
2058:
2059: if (temp.length() == 0) {
2060: temp.append("none");
2061: }
2062:
2063: response = temp.toString();
2064:
2065: if (response.indexOf(" 200 ") == -1) {
2066: throw new IOException(
2067: "Error initializing HTTP tunnel connection: \n"
2068: + response);
2069: }
2070: }
2071:
2072: /**
2073: * Check the initial response message looking for the
2074: * appropriate HTTP version string. Parse the response
2075: * code for easy application branching on condition codes.
2076: *
2077: * @param in input stream where the response headers are read
2078: * @exception IOException is thrown if the header response can
2079: * not be parsed
2080: */
2081: private void readResponseMessage(InputStream in) throws IOException {
2082:
2083: String line = null;
2084: responseCode = -1;
2085: responseMsg = null;
2086:
2087: line = readLine(in);
2088:
2089: /*
2090: * REFERENCE: HTTP1.1 document
2091: * SECTION: 3.6.1 Chunked Transfer Coding
2092: * in some cases there may be an OPTIONAL trailer containing
2093: * entity-header fields. since we don't support the available()
2094: * method for inputstreams and for performance reasons we
2095: * do not attempt to clean up the previous connections input
2096: * stream. the first thing we do here is read the stream and
2097: * discard it.
2098: */
2099: if (line != null && line.length() == 0) {
2100: line = readLine(in);
2101: }
2102:
2103: int httpEnd, codeEnd;
2104:
2105: responseCode = -1;
2106: responseMsg = null;
2107:
2108: if (line == null) {
2109: throw new IOException("response empty");
2110: }
2111:
2112: httpEnd = line.indexOf(' ');
2113: if (httpEnd < 0) {
2114: if (line.length() > 10) {
2115: // only put the first 10 chars in the exception
2116: line = line.substring(0, 10);
2117: }
2118:
2119: throw new IOException(
2120: "cannot find status code in response: " + line);
2121: }
2122:
2123: String temp = line.substring(0, httpEnd);
2124: if (!temp.startsWith("HTTP")) {
2125: if (httpEnd > 10) {
2126: // only put the first 10 chars in the exception
2127: temp = temp.substring(0, 10);
2128: }
2129:
2130: throw new IOException("response does not start with HTTP "
2131: + "it starts with: " + temp);
2132: }
2133:
2134: httpVer = temp;
2135: if (line.length() <= httpEnd) {
2136: throw new IOException("status line ends after HTTP version");
2137: }
2138:
2139: codeEnd = line.substring(httpEnd + 1).indexOf(' ');
2140: if (codeEnd < 0) {
2141: throw new IOException(
2142: "cannot find reason phrase in response");
2143: }
2144:
2145: codeEnd += (httpEnd + 1);
2146: if (line.length() <= codeEnd) {
2147: throw new IOException("status line end after status code");
2148: }
2149:
2150: try {
2151: responseCode = Integer.parseInt(line.substring(httpEnd + 1,
2152: codeEnd));
2153: } catch (NumberFormatException nfe) {
2154: throw new IOException(
2155: "status code in response is not a number");
2156: }
2157:
2158: responseMsg = line.substring(codeEnd + 1);
2159: }
2160:
2161: /**
2162: * Read the response message headers.
2163: * Parse the response headers name value pairs for easy application use.
2164: *
2165: * @param in input stream where the response headers are read
2166: * @exception IOException is thrown if the response headers cannot
2167: * be parsed
2168: */
2169: private void readHeaders(InputStream in) throws IOException {
2170: String line;
2171: String key = null;
2172: int prevPropIndex = headerFields.size() - 1;
2173: boolean firstLine = true;
2174: String value;
2175: String prevValue = null;
2176: int index;
2177:
2178: /*
2179: * Initialize and set the current input stream variables
2180: */
2181: bytesleft = 0;
2182: chunksize = -1;
2183: bytesread = 0;
2184: totalbytesread = 0;
2185: chunkedIn = false;
2186: eof = false;
2187:
2188: for (;;) {
2189: try {
2190: line = readLine(in);
2191: } catch (IOException ioe) {
2192: throw new IOException(ioe.getMessage());
2193: }
2194:
2195: if (line == null || line.equals(""))
2196: break;
2197:
2198: if ((!firstLine)
2199: && (line.charAt(0) == ' ' || line.charAt(0) == '\t')) {
2200: // This line is a continuation of the previous line.
2201:
2202: /*
2203: * The continuation is for the user readablility so restore
2204: * the CR LF when appending.
2205: */
2206: value = prevValue + "\r\n" + line;
2207:
2208: /*
2209: * Set value by index, since there can be multiple properties
2210: * with the same key.
2211: */
2212: headerFields.setPropertyAt(prevPropIndex, value);
2213: prevValue = value;
2214: continue;
2215: }
2216:
2217: index = line.indexOf(':');
2218: if (index < 0) {
2219: throw new IOException("malformed header field " + line);
2220: }
2221:
2222: key = line.substring(0, index);
2223: if (key.length() == 0) {
2224: throw new IOException("malformed header field, no key "
2225: + line);
2226: }
2227:
2228: if (line.length() <= index + 1)
2229: value = "";
2230: else
2231: value = line.substring(index + 1).trim();
2232:
2233: /**
2234: * Check the response header to see if the server would like
2235: * to close the connection.
2236: * CR#4492849
2237: */
2238: if ((key.equalsIgnoreCase("connection"))
2239: && (value.equalsIgnoreCase("close"))) {
2240: ConnectionCloseFlag = true;
2241: }
2242:
2243: /*
2244: * Determine if this is a chunked data transfer. Transfer-Encoding
2245: * header values are treated as case-insensitive
2246: */
2247: if ((key.equalsIgnoreCase("transfer-encoding"))
2248: && (value.equalsIgnoreCase("chunked"))) {
2249: chunkedIn = true;
2250: }
2251:
2252: /*
2253: * Update the Content-Length based on the header value.
2254: */
2255: if (key.equalsIgnoreCase("content-length")) {
2256: try {
2257: contentLength = Integer.parseInt(value);
2258: } catch (IllegalArgumentException iae) {
2259: // fall through
2260: } catch (NullPointerException npe) {
2261: // fall through
2262: }
2263: }
2264:
2265: /* Save the response key value pairs. */
2266: headerFields.addProperty(key, value);
2267: firstLine = false;
2268: prevPropIndex++;
2269: prevValue = value;
2270:
2271: }
2272:
2273: /* Initialize the amount of data expected. */
2274: if (chunkedIn) {
2275: chunksize = readChunkSize();
2276: } else {
2277: // do not let the read block if there is no data.
2278: if (method.equals(HEAD)) {
2279: chunksize = 0;
2280: } else {
2281: // treat non chunked data of known length as one big chunk
2282: chunksize = contentLength;
2283: }
2284: }
2285:
2286: /* Last chunk or zero length response data. */
2287: if (chunksize == 0) {
2288: eof = true;
2289: }
2290: }
2291:
2292: /**
2293: * Uses the shared stringbuffer to read a line terminated by CRLF
2294: * and return it as string. Blocks until the line is done or end of
2295: * stream.
2296: *
2297: * @param in InputStream to read the data
2298: * @return one line of input header or null if end of stream
2299: * @exception IOException if error encountered while reading headers
2300: */
2301: private String readLine(InputStream in) throws IOException {
2302: int c;
2303:
2304: try {
2305: for (;;) {
2306: c = in.read();
2307: if (c < 0) {
2308: return null;
2309: }
2310:
2311: if (c == '\r') {
2312: continue;
2313: }
2314:
2315: if (c == '\n') {
2316: break;
2317: }
2318:
2319: stringbuffer.append((char) c);
2320: }
2321:
2322: /* Return a whole line and reset the string buffer. */
2323: String line = stringbuffer.toString();
2324:
2325: return line;
2326: } finally {
2327: stringbuffer.setLength(0);
2328: }
2329: }
2330:
2331: /**
2332: * Close the OutputStream and transition to connected state.
2333: *
2334: * @exception IOException if the subclass throws one
2335: */
2336: protected void closeOutputStream() throws IOException {
2337: try {
2338: /*
2339: * Send a request to the web server if there wasn't one
2340: * sent already
2341: */
2342: sendRequest();
2343:
2344: /* Finish the common close processing. */
2345: super .closeOutputStream();
2346: } catch (Exception e) {
2347: /* Finish the common close processing. */
2348: super .closeOutputStream();
2349: if (e instanceof IOException) {
2350: throw (IOException) e;
2351: }
2352:
2353: throw (RuntimeException) e;
2354: }
2355: }
2356:
2357: /**
2358: * Disconnect the current low level socket connection. If the connection
2359: * is an HTTP1.1 connection that connection will be put back in the pool
2360: * for another session to use to connect.
2361: */
2362: protected void disconnect() throws IOException {
2363: if (streamConnection == null) {
2364: return;
2365: }
2366:
2367: /*
2368: * If the response had content length and it was not chunked
2369: * and the caller did not read more than the content length
2370: * eof was not set, so do that now.
2371: */
2372: if (!eof && !chunkedIn && chunksize >= 0
2373: && totalbytesread == chunksize) {
2374: eof = true;
2375: }
2376:
2377: /*
2378: * reasons for not reusing the connection are:
2379: *
2380: * 1. only part of the chucked request body was sent
2381: * 2. caller left response data in the stream
2382: * 3. it is a 1.0 connection
2383: * 4. there was a signal to close the connection
2384: * 5. reading in progress on this connection in another thread
2385: */
2386: synchronized (streamInput) {
2387: if (readInProgress) {
2388: // do not save the connection
2389: ConnectionCloseFlag = true;
2390: }
2391: }
2392:
2393: if (!requestFinished || !eof || httpVer.equals("HTTP/1.0")
2394: || ConnectionCloseFlag) {
2395: if (streamConnection instanceof StreamConnectionElement) {
2396: // we got this connection from the pool
2397: connectionPool
2398: .remove((StreamConnectionElement) streamConnection);
2399: } else {
2400: disconnect(streamConnection, streamInput, streamOutput);
2401: }
2402:
2403: return;
2404: }
2405:
2406: if (streamConnection instanceof StreamConnectionElement) {
2407: // we got this connection from the pool
2408: connectionPool
2409: .returnForReuse((StreamConnectionElement) streamConnection);
2410: return;
2411: }
2412:
2413: // save the connection for reuse
2414: if (!connectionPool.add(protocol, url.host, url.port,
2415: streamConnection, streamOutput, streamInput)) {
2416: // pool full, disconnect
2417: disconnect(streamConnection, streamInput, streamOutput);
2418: }
2419: }
2420:
2421: /**
2422: * Disconnect from the underlying socket transport.
2423: * Closes the low level socket connection and the input and
2424: * output streams used by the socket.
2425: * <p>
2426: * Warning: A subclass that implements connect, should also implement this
2427: * method without calling this method.
2428: *
2429: * @param connection connection return from {@link #connect()}
2430: * @param inputStream input stream opened from <code>connection</code>
2431: * @param outputStream output stream opened from <code>connection</code>
2432: * @exception IOException if an I/O error occurs while
2433: * the connection is terminated.
2434: * @exception IOException is thrown if the connection or
2435: * associated streams cannot be closed
2436: */
2437: protected void disconnect(StreamConnection connection,
2438: InputStream inputStream, OutputStream outputStream)
2439: throws IOException {
2440: try {
2441: if (connection != null) {
2442: connection.close();
2443: }
2444: } finally {
2445: try {
2446: if (outputStream != null) {
2447: outputStream.close();
2448: }
2449: } finally {
2450: if (inputStream != null) {
2451: inputStream.close();
2452: }
2453: }
2454: }
2455: }
2456: }
|