/* * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package com.sun.cdc.io.j2me.http; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; import java.net.URL; import java.net.MalformedURLException; import java.util.Hashtable; import java.util.Enumeration; import javax.microedition.io.StreamConnection; import javax.microedition.io.HttpConnection; import javax.microedition.io.Connector; import com.sun.cdc.io.ConnectionBase; import com.sun.cdc.io.DateParser; import java.net.SocketException; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import javax.microedition.io.ConnectionNotFoundException; import sun.misc.NetworkMetrics; import sun.misc.NetworkMetricsInf; /** * This class implements the necessary functionality * for an HTTP connection. */ public class Protocol extends ConnectionBase implements HttpConnection { protected String url; protected String protocol; protected String host; private String file; private String ref; private String query; protected int port = 80; protected int responseCode; protected String responseMsg; protected Hashtable reqProperties; protected Hashtable headerFields; private String[] headerFieldNames; private String[] headerFieldValues; protected String method; protected int opens; protected int mode; protected boolean connected; /* there should be only one outputstream opened at any time */ protected boolean outputStreamOpened = false; private boolean closed = false; /* * In/Out Streams used to buffer input and output */ private PrivateInputStream privateIn; protected PrivateOutputStream privateOut; /* * The streams from the underlying socket connection. */ protected StreamConnection streamConnection; protected DataOutputStream streamOutput; protected DataInputStream streamInput; /** Maximum number of persistent connections. */ protected static int maxNumberOfPersistentConnections; /** Connection linger time in the pool, default 60 seconds. */ protected static long connectionLingerTime; protected StreamConnectionPool connectionPool; private static StreamConnectionPool staticConnectionPool; private static String platformUserAgent; private static String platformWapProfile; protected NetworkMetricsInf nm; protected boolean sentMetric = false; protected int totalBytesSent = 0; protected int totalBytesRead = 0; static { maxNumberOfPersistentConnections = Integer.parseInt( (String) AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "microedition.maxpersconn", "10"))); connectionLingerTime = Integer.parseInt( (String) AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "microedition.connlinger", "60000"))); staticConnectionPool = new StreamConnectionPool(maxNumberOfPersistentConnections, connectionLingerTime); platformUserAgent = (String) AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "platform.browser.user.agent", null)); platformWapProfile = (String)AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "platform.browser.wap.profile", null)); } /* * A shared temporary buffer used in a couple of places */ protected StringBuffer stringbuffer; private String proxyHost = null; private int proxyPort = 80; protected final String httpVersion = "HTTP/1.1"; /** Used when appl calls setRequestProperty("Connection", "close"). */ private boolean ConnectionCloseFlag; private String responseProtocol; /** * create a new instance of this class. * We are initially unconnected. */ public Protocol() { connectionPool = getConnectionPool(); reqProperties = new Hashtable(); headerFields = new Hashtable(); stringbuffer = new StringBuffer(32); opens = 0; connected = false; method = GET; responseCode = -1; protocol = "http"; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { String http_proxy; String profileTemp = System.getProperty("microedition.profiles"); if (profileTemp != null && profileTemp.indexOf("MIDP") != -1) { // We want to look for a MIDP property specifying proxies. http_proxy = System.getProperty("com.sun.midp.io.http.proxy"); } else { // Default to CDC http_proxy = System.getProperty("com.sun.cdc.io.http.proxy"); } parseProxy(http_proxy); return null; } }); } /* * Return the static pool instance for this type of protocol. */ protected StreamConnectionPool getConnectionPool() { return staticConnectionPool; } /* * Check permission to connect to the indicated host. * This should be overriden by the MIDP protocol handler * to check the proper MIDP permission. */ protected void checkPermission(String host, int port, String file) { // Check for SecurityManager.checkConnect() java.lang.SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkConnect(host, port); } return; } /* * Check permission when opening an OutputStream. MIDP * versions of the protocol handler should override this * with an empty method. Throw a SecurityException if * the connection is not allowed. */ protected void outputStreamPermissionCheck() { // Check for SecurityManager.checkConnect() java.lang.SecurityManager sm = System.getSecurityManager(); if (sm != null) { if (host != null) { sm.checkConnect(host, port); } else { sm.checkConnect("localhost", port); } } return; } /* * Check permission when opening an InputStream. MIDP * versions of the protocol handler should override this * with an empty method. A SecurityException will be * raised if the connection is not allowed. */ protected void inputStreamPermissionCheck() { // Check for SecurityManager.checkConnect() java.lang.SecurityManager sm = System.getSecurityManager(); if (sm != null) { if (host != null) { sm.checkConnect(host, port); } else { sm.checkConnect("localhost", port); } } return; } private boolean match(String src, String alphabet) { src = src.toLowerCase(); for (int i = 0; i < src.length(); ++i) { if (-1 == alphabet.indexOf(src.charAt(i))) { return false; } } return true; } private void validateHost() { final String ALPHANUM = "0123456789abcdefghijklmnopqrstuvwxyz-"; final String DIGITS = "0123456789"; final String HEX = "abcdef"; final int TLD = -1; if (host.endsWith(".") || host.startsWith(".")) { throw new IllegalArgumentException("Invalid host name: "+host); } int i = 0; int[] ip4addr = new int[4]; int ip4n = 0; boolean ip4 = true; boolean ip6 = host.startsWith("[") && host.endsWith("]"); if (ip6) { // remove [ and ] for IPv6: host = host.substring(1, host.length()-1); ip4 = false; } try { boolean error = false; String sep = ip6 ? ":" : "."; do { int n = host.indexOf(sep, i); // -1 means top-level domain String domain = host.substring(i, n == TLD ? host.length() : n); if (ip6) { if (!match(domain, DIGITS+HEX) && !"".equals(domain)){ error = true; break; } } else { if (domain.equals("") || !match(domain, ALPHANUM) // Minus sign cannot be first or last symbol || domain.startsWith("-") || domain.endsWith("-") // TLD cannot start with a digit || (n == TLD && !ip4 && match(domain.substring(0, 1), DIGITS)) // Only for numbers in IPv4 address || (ip4 && ip4n >= 4)) { error = true; break; } if (ip4 && match(domain, DIGITS+HEX+'x')) { if (match(domain, DIGITS)) { // decimal ip4addr[ip4n] = Integer.parseInt(domain, 10); } else { if (domain.startsWith("0x")) { // hexadecimal ip4addr[ip4n] = Integer.parseInt(domain.substring(2), 16); } else { ip4 = false; } } } else { ip4 = false; } ip4n++; } i = n+1; } while (i != 0); if (error) { throw new IllegalArgumentException("Invalid host name: "+host); } } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid host name: "+host); } // Check IPv4 address range if (ip4 && ip4n == 4) { for (i = 0; i < 4; ++i) { if (ip4addr[i] < 0 || ip4addr[i] > 255) { throw new IllegalArgumentException( "Invalid host name: " + host); } } } } /* * To pass CDC TCK (which should not be testing HTTP) only. * (not needed for FP and MIDP TCKs) * This can be overrided by non-CDC profiles to just return false. */ protected boolean isNoSuchHost() { if (host.equals("no.such.host")) { return true; } return false; } public void open(String url, int mode, boolean timeouts) throws IOException { open1(url, mode, timeouts); } protected void open1(String url, int mode, boolean timeouts) throws IOException { // DEBUG: System.out.println(this + ".open(" + url + ")"); if (opens > 0) { throw new IOException("already connected"); } opens = 1; closed = false; if (mode != Connector.READ && mode != Connector.WRITE && mode != Connector.READ_WRITE) { throw new IOException("illegal mode: " + mode); } this.url = url; this.mode = mode; parseURL(); validateHost(); // Check permission. The permission method wants the URL checkPermission(host, port, file); /* * Holding off the connecting to the server until the application * has fully setup the request helps avoid connection failures * against real world servers that require sending data immediately * after connecting, so the FP and MIDP specs for HttpConnection * specify this behavior. * * The CDC (not the FP or MIDP) TCK tests Connector.open with "http" * when it should not and then incorrectly expects the connection * to happen during Connector.open when the FP and MIDP specs say * otherwise. Serveral tests open the connection with an unknown host * and expects ConnectionNotFoundException to be thrown. * * While waiting for the CDC TCK to be changed we will workaround the * CDC TCK by rejecting the "no.such.host" host during open. */ if (isNoSuchHost()) { throw new ConnectionNotFoundException("Unknown host"); } } protected void getStreamConnection() throws IOException { if (streamConnection == null) { try { streamConnection = connectSocket(); } catch (UnknownHostException ex) { throw new ConnectionNotFoundException( "Cannot connect to "+host+" on port "+port+": "+ex); } catch (SocketException ex) { throw new ConnectionNotFoundException( "Cannot connect to "+host+" on port "+port+": "+ex); } } } public void close() throws IOException { // DEBUG: System.out.println ("close " + opens + " " + connected ); /* Decrement opens only once - there could be multiple close() calls */ if (!closed) { --opens; closed = true; } if (opens == 0 && streamConnection != null) { disconnect(); } } /* * Open the input stream if it has not already been opened. * @exception IOException is thrown if it has already been * opened for writing */ public InputStream openInputStream() throws IOException { // DEBUG: System.out.println ("open input stream"); inputStreamPermissionCheck(); /* CR 6226615: opening another stream should not throw IOException if (in != null) { throw new IOException("already open"); } */ // If the connection was opened and closed before the // data input stream is accessed, throw an IO exception if (opens == 0) { throw new IOException("connection is closed"); } // Check that the connection was opened for reading if (mode != Connector.READ && mode != Connector.READ_WRITE) { throw new IOException("write-only connection"); } connect(); opens++; privateIn = new PrivateInputStream(); return privateIn; } public DataInputStream openDataInputStream() throws IOException { /* CR 6226615: opening another stream should not throw IOException if (appDataIn != null) { throw new IOException("already open"); } */ openInputStream(); return new DataInputStream(privateIn); } public OutputStream openOutputStream() throws IOException { // Delegate to openDataOutputStream return openDataOutputStream(); } public DataOutputStream openDataOutputStream() throws IOException { outputStreamPermissionCheck(); // DEBUG: System.out.println ("open data output stream"); if (mode != Connector.WRITE && mode != Connector.READ_WRITE) { throw new IOException("read-only connection"); } // If the connection was opened and closed before the // data output stream is accessed, throw an IO exception if (opens == 0) { throw new IOException("connection is closed"); } if (privateIn != null) { throw new IOException( "cannot open output stream while input stream is open"); } /* CR 6226615: opening another stream should not throw IOException if (out != null) { throw new IOException("already open"); } */ opens++; privateOut = new PrivateOutputStream(); outputStreamOpened = true; return new DataOutputStream(privateOut); } /** * PrivateInputStream to handle chunking for HTTP/1.1. */ protected class PrivateInputStream extends InputStream { int bytesleft; // Number of bytes left in current chunk boolean chunked; // true if Transfer-Encoding: chunked boolean eof; // true if eof seen PrivateInputStream() throws IOException { bytesleft = 0; chunked = false; eof = false; // Determine if this is a chunked datatransfer and setup String te = (String)headerFields.get("transfer-encoding"); if (te != null && te.equals("chunked")) { chunked = true; bytesleft = readChunkSize(); eof = (bytesleft == 0); } if (te == null) { // CR 6211256 // If there is no Transfer-Encoding header, a Content-Length // header may tell us how much to read. We will treat this // as a big "logical chunk" and set the number of bytes left // to the header's value. If we cannot parse the // value, throw an IOException as we would if we couldn't // read a chunk size; according to the spec, the User Agent // should notify the user that the value is bad. This CR // was filed against the Https procotol handler specifically, // but is included here because we should pay attention // to the content-length header, and to keep the two // PrivateInputStream classes in sync. String cl = (String)headerFields.get("content-length"); if (cl != null) { try { // Parse the content-length. If it fails to parse or // is < 0 it is invalid. bytesleft = Integer.parseInt(cl); } catch (NumberFormatException nfe) { // Deliberately set bytesleft to a bogus value bytesleft = -1; } finally { if (bytesleft < 0) { throw new IOException("Bad Content-Length value"); } eof = (bytesleft == 0); } } } } /** * Returns the number of bytes that can be read (or skipped over) * from this input stream without blocking by the next caller of * a method for this input stream. * * This method simply returns the number of bytes left from a * chunked response from an HTTP 1.1 server, or the remainder * of the Content-Length value if the response is not chunked. */ public int available() throws IOException { // DEBUG: System.out.println("available " + bytesleft + " " + connected); if (connected) { if (bytesleft > 0) { return bytesleft; } else { return streamInput.available(); } } else { throw new IOException("connection is not open"); } } /** * Reads the next byte of data from the input stream. The value byte is * returned as an <code>int</code> in the range <code>0</code> to * <code>255</code>. If no byte is available because the end of the * stream has been reached, the value <code>-1</code> is returned. This * method blocks until input data is available, the end of the stream is * detected, or an exception is thrown. * * <p> A subclass must provide an implementation of this method. * * @return the next byte of data, or <code>-1</code> if the end of * the stream is reached. * @exception IOException if an I/O error occurs. */ public int read() throws IOException { // Be consistent about returning EOF once encountered. if (eof) { return -1; } /* * If all the current chunk has been read and this * is a chunked transfer then read the next chunk length. */ if (bytesleft <= 0 && chunked) { readCRLF(); // Skip trailing \r\n bytesleft = readChunkSize(); if (bytesleft == 0) { // end of the chunks, read the 'trailer' try { while (streamInput.available() > 0) { int t = streamInput.read(); if (t == '\r') { if (streamInput.available() <= 0) { break; } t = streamInput.read(); if (t != '\n') { break; } totalBytesRead += 2; break; } } } catch (Exception e) {} eof = true; return -1; } } int ch = streamInput.read(); totalBytesRead++; bytesleft--; // CR 6211256 // If we read an EOF, or if we are not chunked but // we've read all we expect to see (i.e., the Content-Length // header was set), then note that we've hit EOF. eof = (ch == -1) || (!chunked && bytesleft == 0); return ch; } /* * Reads up to <code>len</code> bytes of data from the input stream into * an array of bytes. An attempt is made to read as many as * <code>len</code> bytes, but a smaller number may be read, possibly * zero. The number of bytes actually read is returned as an integer. * * This method allows direct consumer-supplier connection * to avoid default byte-by-byte reading behaviour. */ public int read(byte[] b, int off, int len) throws IOException { /* * Need to check parameters here, because len may be changed * and streamInput.read() will not notice invalid argument. */ if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } // Be consistent about returning EOF once encountered. if (eof) return -1; if ((chunked) && (bytesleft <= 0)) { readCRLF(); // Skip trailing \r\n bytesleft = readChunkSize(); if (bytesleft == 0) { // end of the chunks, read the 'trailer' try { while (streamInput.available() > 0) { int t = streamInput.read(); if (t == '\r') { if (streamInput.available() <= 0) { break; } t = streamInput.read(); if (t != '\n') { break; } totalBytesRead += 2; break; } } } catch (Exception e) {} eof = true; return -1; } } /* * Don't read more than was specified as available . * len will remain > 0, because * if bytesleft is 0, than eof was also true. */ if (len > bytesleft) { len = bytesleft; } int bytesRead = streamInput.read(b, off, len); if (bytesRead < 0) { eof = true; } else { totalBytesRead += bytesRead; bytesleft -= bytesRead; eof = (!chunked) && (bytesleft <= 0); } return bytesRead; } /* * Read the chunk size from the input. * It is a hex length followed by optional headers (ignored). * and terminated with <cr><lf>. */ private int readChunkSize() throws IOException { int size = -1; try { String chunk = readLine(streamInput); if (chunk == null) { throw new IOException("No Chunk Size"); } totalBytesRead += chunk.length(); int i; for (i = 0; i < chunk.length(); i++) { char ch = chunk.charAt(i); if (Character.digit(ch, 16) == -1) break; } // look at extensions?.... size = Integer.parseInt(chunk.substring(0, i), 16); } catch (NumberFormatException e) { throw new IOException("Bogus chunk size"); } return size; } /* * Read <cr><lf> from the InputStream. * @exception IOException is thrown if either <CR> or <LF> * is missing. */ private void readCRLF() throws IOException { int ch; ch = streamInput.read(); if (ch != '\r') throw new IOException("missing CRLF"); ch = streamInput.read(); if (ch != '\n') throw new IOException("missing CRLF"); totalBytesRead += 2; } public void close() throws IOException { // DEBUG: System.out.println("close input stream "+opens+" " + connected); if (opens == 0) return; if (--opens == 0 && connected) disconnect(); } } /** * Private OutputStream to allow the buffering of output * so the "Content-Length" header can be supplied. */ protected class PrivateOutputStream extends ByteArrayOutputStream { public void flush() throws IOException { // DEBUG: System.out.println("flush output stream"); super.flush(); // Unlike close(), no data written, then no connection needed if (size() > 0) { connect(); } } public void close() throws IOException { // DEBUG: System.out.println("close output stream"+opens + " "+connected); // CR 6216611: If the connection is already closed, just return if (opens == 0) return; flush(); /* * Close must connect and flush may not connect. * Connect will handle multiple calls. */ connect(); if (--opens == 0 && connected) disconnect(); outputStreamOpened = false; } }// PrivateOutputStream protected void ensureOpen() throws IOException { if (opens == 0) throw new IOException("Connection closed"); } public String getURL() { // RFC: Add back protocol stripped by Content Connection. return protocol + ":" + url; } public String getProtocol() { return protocol; } public String getHost() { return (host.length() == 0 ? null : host); } public String getFile() { return (file.length() == 0 ? null : file); } public String getRef() { return (ref.length() == 0 ? null : ref); } public String getQuery() { return (query.length() == 0 ? null : query); } public int getPort() { return port; } public String getRequestMethod() { return method; } public void setRequestMethod(String method) throws IOException { // DEBUG: System.out.println("setRequestMethod("+ method + ")"); ensureOpen(); if (connected) throw new IOException("connection already open"); if (!method.equals(HEAD) && !method.equals(GET) && !method.equals(POST)) { throw new IOException("unsupported method: " + method); } // ignore the request if the outputstream is already open if (outputStreamOpened) return; this.method = new String(method); } public String getRequestProperty(String key) { // DEBUG: System.out.println("getRequestProperty("+ key + ")"); return (String)reqProperties.get(key); } private static boolean validateProperty(String s) { boolean res = true; if (s.endsWith("\r") || s.endsWith("\n")) { res = false; } else { // Check spaces after each \r\n for (int i = s.indexOf('\r'); i != -1; i = s.indexOf('\r', i+1)) { if (s.charAt(i+1) != '\n' || (s.charAt(i+2) != ' ' && s.charAt(i+2) != '\t')) { res = false; break; } } } return res; } public void setRequestProperty(String key, String value) throws IOException { // DEBUG: System.out.println("setRequestProperty("+ key + ", " + value + ")"); ensureOpen(); if (connected) throw new IOException("connection already open"); if (outputStreamOpened) return; if (!validateProperty(value)) throw new IllegalArgumentException("Illegal request property"); /* * If application setRequestProperties("Connection", "close") * then we need to know this & take appropriate default close action */ if ((key.equalsIgnoreCase("connection")) && (value.equalsIgnoreCase("close"))) { ConnectionCloseFlag = true; } reqProperties.put(key, value); } public int getResponseCode() throws IOException { // DEBUG: System.out.println("getResponseCode"); ensureOpen(); connect(); return responseCode; } public String getResponseMessage() throws IOException { // DEBUG: System.out.println("getResponseMessage"); ensureOpen(); connect(); return responseMsg; } public long getLength() { // DEBUG: System.out.println("getLength"); try { connect(); } catch (IOException x) { return -1; } try { return getHeaderFieldInt("content-length", -1); } catch (IOException e) { return -1; } } public String getType() { // DEBUG: System.out.println("getType"); try { connect(); } catch (IOException x) { return null; } try { return getHeaderField("content-type"); } catch (IOException e) { return null; } } public String getEncoding() { // DEBUG: System.out.println("getEncoding"); try { connect(); } catch (IOException x) { return null; } try { return getHeaderField("content-encoding"); } catch (Exception e) { return null; } } public long getExpiration() throws IOException { // DEBUG: System.out.println("getExpiration"); return getHeaderFieldDate("expires", 0); } public long getDate() throws IOException { // DEBUG: System.out.println("getDate"); return getHeaderFieldDate("date", 0); } public long getLastModified() throws IOException { // DEBUG: System.out.println("getLastModified"); return getHeaderFieldDate("last-modified", 0); } public String getHeaderField(String name) throws IOException { // DEBUG: System.out.println("getHeaderField(" + name + ")"); ensureOpen(); connect(); if (name == null) { return null; } return (String)headerFields.get(toLowerCase(name)); } public String getHeaderField(int index) throws IOException { // DEBUG: System.out.println("getHeaderField(" + index + ")"); ensureOpen(); connect(); if (headerFieldValues == null) { makeHeaderFieldValues(); } if (index >= headerFieldValues.length) return null; return headerFieldValues[index]; } public String getHeaderFieldKey(int index) throws IOException { // DEBUG: System.out.println("getHeaderFieldKey(" + index + ")"); ensureOpen(); connect(); if (headerFieldNames == null) { makeHeaderFields(); } if (index >= headerFieldNames.length) return null; return headerFieldNames[index]; } private void makeHeaderFields() { int i = 0; headerFieldNames = new String [ headerFields.size() ]; for (Enumeration e = headerFields.keys(); e.hasMoreElements(); headerFieldNames[i++] = (String)e.nextElement()); } private void makeHeaderFieldValues() { int i = 0; headerFieldValues = new String [ headerFields.size() ]; for (Enumeration e = headerFields.keys(); e.hasMoreElements(); headerFieldValues[i++] = (String) headerFields.get(e.nextElement())); } public int getHeaderFieldInt(String name, int def) throws IOException { String field = getHeaderField(name); if (field == null) { return def; } try { return Integer.parseInt(field); } catch (NumberFormatException nfe) { // fall through } catch (IllegalArgumentException iae) { // fall through } return def; } public long getHeaderFieldDate(String name, long def) throws IOException { String field = getHeaderField(name); if (field == null) { return def; } try { return DateParser.parse(field); } catch (NumberFormatException nfe) { // fall through } catch (IllegalArgumentException iae) { // fall through } return def; } protected StreamConnection connectSocket() throws IOException { // Check for illegal empty string for host if (host.equals("")) { throw new IllegalArgumentException("Host not recognized: "+host); } // Open socket connection. StreamConnection hsc = null; if (proxyHost == null) { hsc = connectionPool.get(protocol, host, port); } else { hsc = connectionPool.get(protocol, proxyHost, proxyPort); } if (hsc != null) { return hsc; } if (proxyHost == null) { hsc = new HttpStreamConnection(host, port); } else { hsc = new HttpStreamConnection(proxyHost, proxyPort); } return hsc; } protected void sendRequest() throws IOException { // DEBUG: System.out.println("sendRequest"); streamOutput = streamConnection.openDataOutputStream(); // HTTP 1.1 requests must contain content length for proxies if ((getRequestProperty("Content-Length") == null) || (getRequestProperty("Content-Length").equals("0"))) { reqProperties.put("Content-Length", "" + (privateOut == null ? 0 : privateOut.size())); } String reqLine; if (proxyHost == null) { reqLine = method + " " + (getFile() == null ? "/" : getFile()) + (getRef() == null ? "" : "#" + getRef()) + (getQuery() == null ? "" : "?" + getQuery()) + " " + httpVersion + "\r\n"; } else { reqLine = method + " http://" + host + ":" + port + (getFile() == null ? "/" : getFile()) + (getRef() == null ? "" : "#" + getRef()) + (getQuery() == null ? "" : "?" + getQuery()) + " " + httpVersion + "\r\n"; } // DEBUG: System.out.print("Request: " + reqLine); streamOutput.write((reqLine).getBytes()); totalBytesSent = reqLine.length(); // HTTP 1/1 requests require the Host header to distinguish virtual // host locations. reqProperties.put("Host", host + ":" + port); Enumeration reqKeys = reqProperties.keys(); while (reqKeys.hasMoreElements()) { String key = (String)reqKeys.nextElement(); String reqPropLine = key + ": " + reqProperties.get(key) + "\r\n"; // DEBUG: System.out.print(" " + reqPropLine); streamOutput.write((reqPropLine).getBytes()); totalBytesSent += reqPropLine.length(); } // DEBUG: System.out.println(""); streamOutput.write("\r\n".getBytes()); totalBytesSent += 2; if (privateOut != null) { byte[] temp = privateOut.toByteArray(); streamOutput.write(temp); // ***Bug 4485901*** streamOutput.write("\r\n".getBytes()); // DEBUG: System.out.print(" privateOut: "); // DEBUG: System.out.println(binaryToTraceString(temp)); totalBytesSent += temp.length; } java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { if (NetworkMetrics.metricsAvailable()) { StreamConnection con = streamConnection; int methodType = (method.equals(POST) ? NetworkMetricsInf.POST : method.equals(HEAD) ? NetworkMetricsInf.HEAD : NetworkMetricsInf.GET); Class nmClass = NetworkMetrics.getImpl(); if (nmClass == null) { return null; } try { nm = (NetworkMetricsInf) nmClass.newInstance(); } catch (Exception e) { return null; } nm.initReq(NetworkMetricsInf.HTTP, getHost(), getPort(), getFile(), getRef(), getQuery()); if (con instanceof StreamConnectionElement) { con = ((StreamConnectionElement)con).getBaseConnection(); } nm.sendMetricReq(con, methodType, totalBytesSent); sentMetric = true; } return null; } }); streamOutput.flush(); totalBytesRead = 0; streamInput = streamConnection.openDataInputStream(); } protected void connect() throws IOException { if (connected) { return; } String origUserAgentValue = getRequestProperty("User-Agent"); if (origUserAgentValue == null) { origUserAgentValue = ""; } if (platformUserAgent != null && -1 == origUserAgentValue.indexOf(platformUserAgent)) { reqProperties.put("User-Agent", origUserAgentValue + " " + platformUserAgent); } String origWapProfileValue = getRequestProperty("x-wap-profile"); if (origWapProfileValue == null) { origWapProfileValue = ""; } if (platformWapProfile != null && -1 == origWapProfileValue.indexOf(platformWapProfile)) { reqProperties.put("x-wap-profile", platformWapProfile); } String platformNetworkType = (String)AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "platform.browser.network.type", null)); String origNetworkTypeValue = getRequestProperty("network-type"); if (origNetworkTypeValue == null) { origNetworkTypeValue = ""; } if (platformNetworkType != null && -1 == origNetworkTypeValue.indexOf(platformNetworkType)) { reqProperties.put("network-type", platformNetworkType); } getStreamConnection(); // DEBUG: System.out.println("Calling sendRequest"); sendRequest(); try { readResponseMessage(); } catch (InterruptedIOException ex) { if (responseMsg == null) { // Most probably the server has closed its end by timeout. // Here we have to reconnect and resend the request getStreamConnection(); // DEBUG: System.out.println("Calling sendRequest again"); sendRequest(); readResponseMessage(); } else { throw ex; } } if (privateOut != null) { privateOut.reset(); } readHeaders(); connected = true; } protected void readResponseMessage() throws IOException { String line = readLine(streamInput); int httpEnd, codeEnd; responseCode = -1; responseMsg = null; responseProtocol = null; // DEBUG: System.out.println ("Response: " + line); malformed: { if (line == null) break malformed; httpEnd = line.indexOf(' '); if (httpEnd < 0) break malformed; responseProtocol = line.substring(0, httpEnd); if (!responseProtocol.startsWith("HTTP")) break malformed; if (line.length() <= httpEnd) break malformed; codeEnd = line.substring(httpEnd + 1).indexOf(' '); if (codeEnd < 0) break malformed; codeEnd += (httpEnd + 1); if (line.length() <= codeEnd) break malformed; try { responseCode = Integer.parseInt(line.substring(httpEnd + 1, codeEnd)); } catch (NumberFormatException nfe) { break malformed; } totalBytesRead += line.length(); responseMsg = line.substring(codeEnd + 1); return; } disconnect(); throw new InterruptedIOException("malformed response message"); } protected void readHeaders() throws IOException { String line, key = null, value = null; int index; for (;;) { line = readLine(streamInput); // DEBUG: System.out.println ("Response: " + line); if (line == null || line.equals("")) break; totalBytesRead += line.length(); /* * There can be multiline values. The line starts with a space * in that case. */ if (line.charAt(0) == ' ' || line.charAt(0) == '\t') { index = 0; // Replace multiple spaces with a single space while (line.charAt(index) == ' ' || line.charAt(index) == '\t') { ++index; } value += " " + line.substring(index); } else { if (key != null && value != null) { headerFields.put(toLowerCase(key), value); key = null; value = null; } index = line.indexOf(':'); if (index < 0) throw new IOException("malformed header field"); key = line.substring(0, index); if (key.length() == 0) throw new IOException("malformed header field"); if (line.length() <= index + 2) value = ""; else value = line.substring(index + 2); } } if (key != null && value != null) { headerFields.put(toLowerCase(key), value); } } /* * Uses the shared stringbuffer to read a line * terminated by <cr><lf> and return it as string. */ protected String readLine(InputStream in) { int c; stringbuffer.setLength(0); for (;;) { try { c = in.read(); if (c < 0) { return null; } if (c == '\r') { totalBytesRead++; continue; } } catch (IOException ioe) { return null; } if (c == '\n') { totalBytesRead++; break; } stringbuffer.append((char)c); } return stringbuffer.toString(); } protected void disconnect() throws IOException { if (streamConnection == null) return; if (sentMetric && NetworkMetrics.metricsAvailable()) { java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { try { StreamConnection con = streamConnection; if (con instanceof StreamConnectionElement) { con = ((StreamConnectionElement)con).getBaseConnection(); } nm.sendMetricResponse(con, responseCode, totalBytesRead); } catch (NullPointerException e) {} return null; } }); } if (streamConnection != null) { String connectionField = (String) headerFields.get("connection"); if (privateIn == null || privateIn.available() > 0 || connectionField != null && (connectionField.equalsIgnoreCase("close") || (responseProtocol != null && responseProtocol.equalsIgnoreCase("HTTP/1.0") && !connectionField.equalsIgnoreCase("keep-alive")))) { if (streamConnection instanceof StreamConnectionElement) { connectionPool.remove( (StreamConnectionElement) streamConnection); streamConnection = null; } else { disconnectSocket(); } } if (streamConnection != null) { if (streamConnection instanceof StreamConnectionElement) { // we got this connection from the pool connectionPool.returnForReuse( (StreamConnectionElement) streamConnection); streamConnection = null; } else { // save the connection for reuse if (!connectionPool.add(protocol, host, port, (StreamConnection)streamConnection, streamOutput, streamInput)) { // pool full, disconnect disconnectSocket(); } } } } privateIn = null; responseCode = -1; responseMsg = null; connected = false; responseProtocol = null; } protected void disconnectSocket() throws IOException { if (streamConnection != null) { streamConnection.close(); if (! (streamConnection instanceof StreamConnectionElement)) { if (streamInput != null) { streamInput.close(); } if (streamOutput != null) { streamOutput.close(); } } streamInput = null; streamOutput = null; streamConnection = null; } } protected synchronized void parseURL() throws IOException { try { URL loc = new URL(url.startsWith("//") ? protocol+":"+url : url); host = loc.getHost(); if (-1 == (port = loc.getPort())) { port = loc.getProtocol().equals("http") ? 80 : 443; } if (null == (file = loc.getPath())) { file = ""; } if (null == (query = loc.getQuery())) { query = ""; } if (null == (ref = loc.getRef())) { ref = ""; } } catch (MalformedURLException ex) { throw new IllegalArgumentException("Malformed URL: "+url); } } // The proxy value, if any, is specified as a host:port // string. Use the convenience routines to parse it. protected synchronized void parseProxy(String proxyVal) { if (proxyVal != null) { try { URL proxyURL = new URL(proxyVal.startsWith("http://") ? proxyVal : "http://" + proxyVal); proxyHost = proxyURL.getHost(); // null is ok. if (-1 == (proxyPort = proxyURL.getPort())) { proxyPort = 80; } } catch (MalformedURLException ex) { throw new IllegalArgumentException("Malformed URL: "+proxyVal); } // DEBUG: System.out.println ("http parseProxy: " + proxyVal + " " + proxyHost + " " + proxyPort); } return; } private String toLowerCase(String string) { // Uses the shared stringbuffer to create a lower case string. stringbuffer.setLength(0); for (int i = 0; i < string.length(); i++) { stringbuffer.append(Character.toLowerCase(string.charAt(i))); } return stringbuffer.toString(); } protected String binaryToTraceString(byte[] arg) { StringBuffer temp = new StringBuffer(); temp.setLength(0); for (int i = 0; i < arg.length; i++) { char current = (char)arg[i]; if (current < ' ' || current > '~') { temp.append('^'); continue; } temp.append(current); } return temp.toString(); } }