1 /*
   2  * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.rmi.transport.proxy;
  26 
  27 import java.io.*;
  28 import java.net.*;
  29 import java.security.PrivilegedAction;
  30 
  31 import sun.rmi.runtime.Log;
  32 
  33 /**
  34  * The HttpSendSocket class extends the java.net.Socket class
  35  * by enclosing the data output stream in, then extracting the input
  36  * stream from, an HTTP protocol transmission.
  37  *
  38  * NOTES:
  39  *
  40  * Since the length of the output request must be known before the
  41  * HTTP header can be completed, all of the output is buffered by
  42  * an HttpOutputStream object until either an attempt is made to
  43  * read from this socket, or the socket is explicitly closed.
  44  *
  45  * On the first read attempt to read from this socket, the buffered
  46  * output is sent to the destination as the body of an HTTP POST
  47  * request.  All reads will then acquire data from the body of
  48  * the response.  A subsequent attempt to write to this socket will
  49  * throw an IOException.
  50  */
  51 class HttpSendSocket extends Socket implements RMISocketInfo {
  52 
  53     /** the host to connect to */
  54     protected String host;
  55 
  56     /** the port to connect to */
  57     protected int port;
  58 
  59     /** the URL to forward through */
  60     protected URL url;
  61 
  62     /** the object managing this connection through the URL */
  63     protected URLConnection conn = null;
  64 
  65     /** internal input stream for this socket */
  66     protected InputStream in = null;
  67 
  68     /** internal output stream for this socket */
  69     protected OutputStream out = null;
  70 
  71     /** the notifying input stream returned to users */
  72     protected HttpSendInputStream inNotifier;
  73 
  74     /** the notifying output stream returned to users */
  75     protected HttpSendOutputStream outNotifier;
  76 
  77     /**
  78      * Line separator string.  This is the value of the line.separator
  79      * property at the moment that the socket was created.
  80      */
  81     private String lineSeparator =
  82         java.security.AccessController.doPrivileged(
  83             (PrivilegedAction<String>) () -> System.getProperty("line.separator"));
  84 
  85     /**
  86      * Create a stream socket and connect it to the specified port on
  87      * the specified host.
  88      * @param host the host
  89      * @param port the port
  90      */
  91     public HttpSendSocket(String host, int port, URL url) throws IOException
  92     {
  93         super((SocketImpl)null);        // no underlying SocketImpl for this object
  94 
  95         if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
  96             RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
  97                 "host = " + host + ", port = " + port + ", url = " + url);
  98         }
  99 
 100         this.host = host;
 101         this.port = port;
 102         this.url = url;
 103 
 104         inNotifier = new HttpSendInputStream(null, this);
 105         outNotifier = new HttpSendOutputStream(writeNotify(), this);
 106     }
 107 
 108     /**
 109      * Create a stream socket and connect it to the specified port on
 110      * the specified host.
 111      * @param host the host
 112      * @param port the port
 113      */
 114     public HttpSendSocket(String host, int port) throws IOException
 115     {
 116         this(host, port, new URL("http", host, port, "/"));
 117     }
 118 
 119     /**
 120      * Create a stream socket and connect it to the specified address on
 121      * the specified port.
 122      * @param address the address
 123      * @param port the port
 124      */
 125     public HttpSendSocket(InetAddress address, int port) throws IOException
 126     {
 127         this(address.getHostName(), port);
 128     }
 129 
 130     /**
 131      * Indicate that this socket is not reusable.
 132      */
 133     public boolean isReusable()
 134     {
 135         return false;
 136     }
 137 
 138     /**
 139      * Create a new socket connection to host (or proxy), and prepare to
 140      * send HTTP transmission.
 141      */
 142     public synchronized OutputStream writeNotify() throws IOException
 143     {
 144         if (conn != null) {
 145             throw new IOException("attempt to write on HttpSendSocket after " +
 146                                   "request has been sent");
 147         }
 148 
 149         conn = url.openConnection();
 150         conn.setDoOutput(true);
 151         conn.setUseCaches(false);
 152         conn.setRequestProperty("Content-type", "application/octet-stream");
 153 
 154         inNotifier.deactivate();
 155         in = null;
 156 
 157         return out = conn.getOutputStream();
 158     }
 159 
 160     /**
 161      * Send HTTP output transmission and prepare to receive response.
 162      */
 163     public synchronized InputStream readNotify() throws IOException
 164     {
 165         RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
 166             "sending request and activating input stream");
 167 
 168         outNotifier.deactivate();
 169         out.close();
 170         out = null;
 171 
 172         try {
 173             in = conn.getInputStream();
 174         } catch (IOException e) {
 175             RMIMasterSocketFactory.proxyLog.log(Log.BRIEF,
 176                 "failed to get input stream, exception: ", e);
 177 
 178             throw new IOException("HTTP request failed");
 179         }
 180 
 181         /*
 182          * If an HTTP error response is returned, sometimes an IOException
 183          * is thrown, which is handled above, and other times it isn't, and
 184          * the error response body will be available for reading.
 185          * As a safety net to catch any such unexpected HTTP behavior, we
 186          * verify that the content type of the response is what the
 187          * HttpOutputStream generates: "application/octet-stream".
 188          * (Servers' error responses will generally be "text/html".)
 189          * Any error response body is printed to the log.
 190          */
 191         String contentType = conn.getContentType();
 192         if (contentType == null ||
 193             !conn.getContentType().equals("application/octet-stream"))
 194         {
 195             if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) {
 196                 String message;
 197                 if (contentType == null) {
 198                     message = "missing content type in response" +
 199                         lineSeparator;
 200                 } else {
 201                     message = "invalid content type in response: " +
 202                         contentType + lineSeparator;
 203                 }
 204 
 205                 message += "HttpSendSocket.readNotify: response body: ";
 206                 try {
 207                     BufferedReader din = new BufferedReader(new InputStreamReader(in));
 208                     String line;
 209                     while ((line = din.readLine()) != null)
 210                         message += line + lineSeparator;
 211                 } catch (IOException e) {
 212                 }
 213                 RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, message);
 214             }
 215 
 216             throw new IOException("HTTP request failed");
 217         }
 218 
 219         return in;
 220     }
 221 
 222     /**
 223      * Get the address to which the socket is connected.
 224      */
 225     public InetAddress getInetAddress()
 226     {
 227         try {
 228             return InetAddress.getByName(host);
 229         } catch (UnknownHostException e) {
 230             return null;        // null if couldn't resolve destination host
 231         }
 232     }
 233 
 234     /**
 235      * Get the local address to which the socket is bound.
 236      */
 237     public InetAddress getLocalAddress()
 238     {
 239         try {
 240             return InetAddress.getLocalHost();
 241         } catch (UnknownHostException e) {
 242             return null;        // null if couldn't determine local host
 243         }
 244     }
 245 
 246     /**
 247      * Get the remote port to which the socket is connected.
 248      */
 249     public int getPort()
 250     {
 251         return port;
 252     }
 253 
 254     /**
 255      * Get the local port to which the socket is connected.
 256      */
 257     public int getLocalPort()
 258     {
 259         return -1;      // request not applicable to this socket type
 260     }
 261 
 262     /**
 263      * Get an InputStream for this socket.
 264      */
 265     public InputStream getInputStream() throws IOException
 266     {
 267         return inNotifier;
 268     }
 269 
 270     /**
 271      * Get an OutputStream for this socket.
 272      */
 273     public OutputStream getOutputStream() throws IOException
 274     {
 275         return outNotifier;
 276     }
 277 
 278     /**
 279      * Enable/disable TCP_NODELAY.
 280      * This operation has no effect for an HttpSendSocket.
 281      */
 282     public void setTcpNoDelay(boolean on) throws SocketException
 283     {
 284     }
 285 
 286     /**
 287      * Retrieve whether TCP_NODELAY is enabled.
 288      */
 289     public boolean getTcpNoDelay() throws SocketException
 290     {
 291         return false;   // imply option is disabled
 292     }
 293 
 294     /**
 295      * Enable/disable SO_LINGER with the specified linger time.
 296      * This operation has no effect for an HttpSendSocket.
 297      */
 298     public void setSoLinger(boolean on, int val) throws SocketException
 299     {
 300     }
 301 
 302     /**
 303      * Retrive setting for SO_LINGER.
 304      */
 305     public int getSoLinger() throws SocketException
 306     {
 307         return -1;      // imply option is disabled
 308     }
 309 
 310     /**
 311      * Enable/disable SO_TIMEOUT with the specified timeout
 312      * This operation has no effect for an HttpSendSocket.
 313      */
 314     public synchronized void setSoTimeout(int timeout) throws SocketException
 315     {
 316     }
 317 
 318     /**
 319      * Retrive setting for SO_TIMEOUT.
 320      */
 321     public synchronized int getSoTimeout() throws SocketException
 322     {
 323         return 0;       // imply option is disabled
 324     }
 325 
 326     /**
 327      * Close the socket.
 328      */
 329     public synchronized void close() throws IOException
 330     {
 331         if (out != null) // push out transmission if not done
 332             out.close();
 333     }
 334 
 335     /**
 336      * Return string representation of this pseudo-socket.
 337      */
 338     public String toString()
 339     {
 340         return "HttpSendSocket[host=" + host +
 341                ",port=" + port +
 342                ",url=" + url + "]";
 343     }
 344 }