1 /*
   2  * Copyright (c) 2005, 2008, 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 
  26 package sun.net.httpserver;
  27 
  28 import java.io.*;
  29 import java.net.*;
  30 import javax.net.ssl.*;
  31 import java.util.*;
  32 import java.util.logging.Logger;
  33 import java.text.*;
  34 import com.sun.net.httpserver.*;
  35 
  36 class ExchangeImpl {
  37 
  38     Headers reqHdrs, rspHdrs;
  39     Request req;
  40     String method;
  41     boolean writefinished;
  42     URI uri;
  43     HttpConnection connection;
  44     long reqContentLen;
  45     long rspContentLen;
  46     /* raw streams which access the socket directly */
  47     InputStream ris;
  48     OutputStream ros;
  49     Thread thread;
  50     /* close the underlying connection when this exchange finished */
  51     boolean close;
  52     boolean closed;
  53     boolean http10 = false;
  54 
  55     /* for formatting the Date: header */
  56     private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
  57     private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT");
  58     private static final ThreadLocal<DateFormat> dateFormat =
  59          new ThreadLocal<DateFormat>() {
  60              @Override protected DateFormat initialValue() {
  61                  DateFormat df = new SimpleDateFormat(pattern, Locale.US);
  62                  df.setTimeZone(gmtTZ);
  63                  return df;
  64          }
  65      };
  66 
  67     private static final String HEAD = "HEAD";
  68 
  69     /* streams which take care of the HTTP protocol framing
  70      * and are passed up to higher layers
  71      */
  72     InputStream uis;
  73     OutputStream uos;
  74     LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper
  75     PlaceholderOutputStream uos_orig;
  76 
  77     boolean sentHeaders; /* true after response headers sent */
  78     Map<String,Object> attributes;
  79     int rcode = -1;
  80     HttpPrincipal principal;
  81     ServerImpl server;
  82 
  83     ExchangeImpl (
  84         String m, URI u, Request req, long len, HttpConnection connection
  85     ) throws IOException {
  86         this.req = req;
  87         this.reqHdrs = req.headers();
  88         this.rspHdrs = new Headers();
  89         this.method = m;
  90         this.uri = u;
  91         this.connection = connection;
  92         this.reqContentLen = len;
  93         /* ros only used for headers, body written directly to stream */
  94         this.ros = req.outputStream();
  95         this.ris = req.inputStream();
  96         server = getServerImpl();
  97         server.startExchange();
  98     }
  99 
 100     public Headers getRequestHeaders () {
 101         return new UnmodifiableHeaders (reqHdrs);
 102     }
 103 
 104     public Headers getResponseHeaders () {
 105         return rspHdrs;
 106     }
 107 
 108     public URI getRequestURI () {
 109         return uri;
 110     }
 111 
 112     public String getRequestMethod (){
 113         return method;
 114     }
 115 
 116     public HttpContextImpl getHttpContext (){
 117         return connection.getHttpContext();
 118     }
 119 
 120     private boolean isHeadRequest() {
 121         return HEAD.equals(getRequestMethod());
 122     }
 123 
 124     public void close () {
 125         if (closed) {
 126             return;
 127         }
 128         closed = true;
 129 
 130         /* close the underlying connection if,
 131          * a) the streams not set up yet, no response can be sent, or
 132          * b) if the wrapper output stream is not set up, or
 133          * c) if the close of the input/outpu stream fails
 134          */
 135         try {
 136             if (uis_orig == null || uos == null) {
 137                 connection.close();
 138                 return;
 139             }
 140             if (!uos_orig.isWrapped()) {
 141                 connection.close();
 142                 return;
 143             }
 144             if (!uis_orig.isClosed()) {
 145                 uis_orig.close();
 146             }
 147             uos.close();
 148         } catch (IOException e) {
 149             connection.close();
 150         }
 151     }
 152 
 153     public InputStream getRequestBody () {
 154         if (uis != null) {
 155             return uis;
 156         }
 157         if (reqContentLen == -1L) {
 158             uis_orig = new ChunkedInputStream (this, ris);
 159             uis = uis_orig;
 160         } else {
 161             uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);
 162             uis = uis_orig;
 163         }
 164         return uis;
 165     }
 166 
 167     LeftOverInputStream getOriginalInputStream () {
 168         return uis_orig;
 169     }
 170 
 171     public int getResponseCode () {
 172         return rcode;
 173     }
 174 
 175     public OutputStream getResponseBody () {
 176         /* TODO. Change spec to remove restriction below. Filters
 177          * cannot work with this restriction
 178          *
 179          * if (!sentHeaders) {
 180          *    throw new IllegalStateException ("headers not sent");
 181          * }
 182          */
 183         if (uos == null) {
 184             uos_orig = new PlaceholderOutputStream (null);
 185             uos = uos_orig;
 186         }
 187         return uos;
 188     }
 189 
 190 
 191     /* returns the place holder stream, which is the stream
 192      * returned from the 1st call to getResponseBody()
 193      * The "real" ouputstream is then placed inside this
 194      */
 195     PlaceholderOutputStream getPlaceholderResponseBody () {
 196         getResponseBody();
 197         return uos_orig;
 198     }
 199 
 200     public void sendResponseHeaders (int rCode, long contentLen)
 201     throws IOException
 202     {
 203         if (sentHeaders) {
 204             throw new IOException ("headers already sent");
 205         }
 206         this.rcode = rCode;
 207         String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n";
 208         OutputStream tmpout = new BufferedOutputStream (ros);
 209         PlaceholderOutputStream o = getPlaceholderResponseBody();
 210         tmpout.write (bytes(statusLine, 0), 0, statusLine.length());
 211         boolean noContentToSend = false; // assume there is content
 212         rspHdrs.set ("Date", dateFormat.get().format (new Date()));
 213 
 214         /* check for response type that is not allowed to send a body */
 215 
 216         if ((rCode>=100 && rCode <200) /* informational */
 217             ||(rCode == 204)           /* no content */
 218             ||(rCode == 304))          /* not modified */
 219         {
 220             if (contentLen != -1) {
 221                 Logger logger = server.getLogger();
 222                 String msg = "sendResponseHeaders: rCode = "+ rCode
 223                     + ": forcing contentLen = -1";
 224                 logger.warning (msg);
 225             }
 226             contentLen = -1;
 227         }
 228 
 229         if (isHeadRequest()) {
 230             /* HEAD requests should not set a content length by passing it
 231              * through this API, but should instead manually set the required
 232              * headers.*/
 233             if (contentLen >= 0) {
 234                 final Logger logger = server.getLogger();
 235                 String msg =
 236                     "sendResponseHeaders: being invoked with a content length for a HEAD request";
 237                 logger.warning (msg);
 238             }
 239             noContentToSend = true;
 240             contentLen = 0;
 241         } else { /* not a HEAD request */
 242             if (contentLen == 0) {
 243                 if (http10) {
 244                     o.setWrappedStream (new UndefLengthOutputStream (this, ros));
 245                     close = true;
 246                 } else {
 247                     rspHdrs.set ("Transfer-encoding", "chunked");
 248                     o.setWrappedStream (new ChunkedOutputStream (this, ros));
 249                 }
 250             } else {
 251                 if (contentLen == -1) {
 252                     noContentToSend = true;
 253                     contentLen = 0;
 254                 }
 255                 rspHdrs.set("Content-length", Long.toString(contentLen));
 256                 o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));
 257             }
 258         }
 259         write (rspHdrs, tmpout);
 260         this.rspContentLen = contentLen;
 261         tmpout.flush() ;
 262         tmpout = null;
 263         sentHeaders = true;
 264         if (noContentToSend) {
 265             WriteFinishedEvent e = new WriteFinishedEvent (this);
 266             server.addEvent (e);
 267             closed = true;
 268         }
 269         server.logReply (rCode, req.requestLine(), null);
 270     }
 271 
 272     void write (Headers map, OutputStream os) throws IOException {
 273         Set<Map.Entry<String,List<String>>> entries = map.entrySet();
 274         for (Map.Entry<String,List<String>> entry : entries) {
 275             String key = entry.getKey();
 276             byte[] buf;
 277             List<String> values = entry.getValue();
 278             for (String val : values) {
 279                 int i = key.length();
 280                 buf = bytes (key, 2);
 281                 buf[i++] = ':';
 282                 buf[i++] = ' ';
 283                 os.write (buf, 0, i);
 284                 buf = bytes (val, 2);
 285                 i = val.length();
 286                 buf[i++] = '\r';
 287                 buf[i++] = '\n';
 288                 os.write (buf, 0, i);
 289             }
 290         }
 291         os.write ('\r');
 292         os.write ('\n');
 293     }
 294 
 295     private byte[] rspbuf = new byte [128]; // used by bytes()
 296 
 297     /**
 298      * convert string to byte[], using rspbuf
 299      * Make sure that at least "extra" bytes are free at end
 300      * of rspbuf. Reallocate rspbuf if not big enough.
 301      * caller must check return value to see if rspbuf moved
 302      */
 303     private byte[] bytes (String s, int extra) {
 304         int slen = s.length();
 305         if (slen+extra > rspbuf.length) {
 306             int diff = slen + extra - rspbuf.length;
 307             rspbuf = new byte [2* (rspbuf.length + diff)];
 308         }
 309         char c[] = s.toCharArray();
 310         for (int i=0; i<c.length; i++) {
 311             rspbuf[i] = (byte)c[i];
 312         }
 313         return rspbuf;
 314     }
 315 
 316     public InetSocketAddress getRemoteAddress (){
 317         Socket s = connection.getChannel().socket();
 318         InetAddress ia = s.getInetAddress();
 319         int port = s.getPort();
 320         return new InetSocketAddress (ia, port);
 321     }
 322 
 323     public InetSocketAddress getLocalAddress (){
 324         Socket s = connection.getChannel().socket();
 325         InetAddress ia = s.getLocalAddress();
 326         int port = s.getLocalPort();
 327         return new InetSocketAddress (ia, port);
 328     }
 329 
 330     public String getProtocol (){
 331         String reqline = req.requestLine();
 332         int index = reqline.lastIndexOf (' ');
 333         return reqline.substring (index+1);
 334     }
 335 
 336     public SSLSession getSSLSession () {
 337         SSLEngine e = connection.getSSLEngine();
 338         if (e == null) {
 339             return null;
 340         }
 341         return e.getSession();
 342     }
 343 
 344     public Object getAttribute (String name) {
 345         if (name == null) {
 346             throw new NullPointerException ("null name parameter");
 347         }
 348         if (attributes == null) {
 349             attributes = getHttpContext().getAttributes();
 350         }
 351         return attributes.get (name);
 352     }
 353 
 354     public void setAttribute (String name, Object value) {
 355         if (name == null) {
 356             throw new NullPointerException ("null name parameter");
 357         }
 358         if (attributes == null) {
 359             attributes = getHttpContext().getAttributes();
 360         }
 361         attributes.put (name, value);
 362     }
 363 
 364     public void setStreams (InputStream i, OutputStream o) {
 365         assert uis != null;
 366         if (i != null) {
 367             uis = i;
 368         }
 369         if (o != null) {
 370             uos = o;
 371         }
 372     }
 373 
 374     /**
 375      * PP
 376      */
 377     HttpConnection getConnection () {
 378         return connection;
 379     }
 380 
 381     ServerImpl getServerImpl () {
 382         return getHttpContext().getServerImpl();
 383     }
 384 
 385     public HttpPrincipal getPrincipal () {
 386         return principal;
 387     }
 388 
 389     void setPrincipal (HttpPrincipal principal) {
 390         this.principal = principal;
 391     }
 392 
 393     static ExchangeImpl get (HttpExchange t) {
 394         if (t instanceof HttpExchangeImpl) {
 395             return ((HttpExchangeImpl)t).getExchangeImpl();
 396         } else {
 397             assert t instanceof HttpsExchangeImpl;
 398             return ((HttpsExchangeImpl)t).getExchangeImpl();
 399         }
 400     }
 401 }
 402 
 403 /**
 404  * An OutputStream which wraps another stream
 405  * which is supplied either at creation time, or sometime later.
 406  * If a caller/user tries to write to this stream before
 407  * the wrapped stream has been provided, then an IOException will
 408  * be thrown.
 409  */
 410 class PlaceholderOutputStream extends java.io.OutputStream {
 411 
 412     OutputStream wrapped;
 413 
 414     PlaceholderOutputStream (OutputStream os) {
 415         wrapped = os;
 416     }
 417 
 418     void setWrappedStream (OutputStream os) {
 419         wrapped = os;
 420     }
 421 
 422     boolean isWrapped () {
 423         return wrapped != null;
 424     }
 425 
 426     private void checkWrap () throws IOException {
 427         if (wrapped == null) {
 428             throw new IOException ("response headers not sent yet");
 429         }
 430     }
 431 
 432     public void write(int b) throws IOException {
 433         checkWrap();
 434         wrapped.write (b);
 435     }
 436 
 437     public void write(byte b[]) throws IOException {
 438         checkWrap();
 439         wrapped.write (b);
 440     }
 441 
 442     public void write(byte b[], int off, int len) throws IOException {
 443         checkWrap();
 444         wrapped.write (b, off, len);
 445     }
 446 
 447     public void flush() throws IOException {
 448         checkWrap();
 449         wrapped.flush();
 450     }
 451 
 452     public void close() throws IOException {
 453         checkWrap();
 454         wrapped.close();
 455     }
 456 }