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