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