1 /*
2  * Copyright (c) 2005, 2010, 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 
ExchangeImpl( String m, URI u, Request req, long len, HttpConnection connection )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 
getRequestHeaders()100     public Headers getRequestHeaders () {
101         return new UnmodifiableHeaders (reqHdrs);
102     }
103 
getResponseHeaders()104     public Headers getResponseHeaders () {
105         return rspHdrs;
106     }
107 
getRequestURI()108     public URI getRequestURI () {
109         return uri;
110     }
111 
getRequestMethod()112     public String getRequestMethod (){
113         return method;
114     }
115 
getHttpContext()116     public HttpContextImpl getHttpContext (){
117         return connection.getHttpContext();
118     }
119 
isHeadRequest()120     private boolean isHeadRequest() {
121         return HEAD.equals(getRequestMethod());
122     }
123 
close()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 
getRequestBody()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 
getOriginalInputStream()167     LeftOverInputStream getOriginalInputStream () {
168         return uis_orig;
169     }
170 
getResponseCode()171     public int getResponseCode () {
172         return rcode;
173     }
174 
getResponseBody()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      */
getPlaceholderResponseBody()195     PlaceholderOutputStream getPlaceholderResponseBody () {
196         getResponseBody();
197         return uos_orig;
198     }
199 
sendResponseHeaders(int rCode, long contentLen)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 
write(Headers map, OutputStream os)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      */
bytes(String s, int extra)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 
getRemoteAddress()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 
getLocalAddress()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 
getProtocol()330     public String getProtocol (){
331         String reqline = req.requestLine();
332         int index = reqline.lastIndexOf (' ');
333         return reqline.substring (index+1);
334     }
335 
getSSLSession()336     public SSLSession getSSLSession () {
337         SSLEngine e = connection.getSSLEngine();
338         if (e == null) {
339             return null;
340         }
341         return e.getSession();
342     }
343 
getAttribute(String name)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 
setAttribute(String name, Object value)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 
setStreams(InputStream i, OutputStream o)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      */
getConnection()377     HttpConnection getConnection () {
378         return connection;
379     }
380 
getServerImpl()381     ServerImpl getServerImpl () {
382         return getHttpContext().getServerImpl();
383     }
384 
getPrincipal()385     public HttpPrincipal getPrincipal () {
386         return principal;
387     }
388 
setPrincipal(HttpPrincipal principal)389     void setPrincipal (HttpPrincipal principal) {
390         this.principal = principal;
391     }
392 
get(HttpExchange t)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 
PlaceholderOutputStream(OutputStream os)414     PlaceholderOutputStream (OutputStream os) {
415         wrapped = os;
416     }
417 
setWrappedStream(OutputStream os)418     void setWrappedStream (OutputStream os) {
419         wrapped = os;
420     }
421 
isWrapped()422     boolean isWrapped () {
423         return wrapped != null;
424     }
425 
checkWrap()426     private void checkWrap () throws IOException {
427         if (wrapped == null) {
428             throw new IOException ("response headers not sent yet");
429         }
430     }
431 
write(int b)432     public void write(int b) throws IOException {
433         checkWrap();
434         wrapped.write (b);
435     }
436 
write(byte b[])437     public void write(byte b[]) throws IOException {
438         checkWrap();
439         wrapped.write (b);
440     }
441 
write(byte b[], int off, int len)442     public void write(byte b[], int off, int len) throws IOException {
443         checkWrap();
444         wrapped.write (b, off, len);
445     }
446 
flush()447     public void flush() throws IOException {
448         checkWrap();
449         wrapped.flush();
450     }
451 
close()452     public void close() throws IOException {
453         checkWrap();
454         wrapped.close();
455     }
456 }
457