/* * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.frostwire.android.httpserver; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; public class HttpExchange { Headers reqHdrs, rspHdrs; Request req; String method; URI uri; HttpConnection connection; int reqContentLen; long rspContentLen; /* raw streams which access the socket directly */ InputStream ris; OutputStream ros; Thread thread; /* close the underlying connection when this exchange finished */ boolean close; boolean closed; boolean http10 = false; /* for formatting the Date: header */ static TimeZone tz; static DateFormat df; static { String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz"; tz = TimeZone.getTimeZone("GMT"); df = new SimpleDateFormat(pattern, Locale.US); df.setTimeZone(tz); } /* streams which take care of the HTTP protocol framing * and are passed up to higher layers */ InputStream uis; OutputStream uos; LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper PlaceholderOutputStream uos_orig; boolean sentHeaders; /* true after response headers sent */ Map<String, Object> attributes; int rcode = -1; HttpServer server; public HttpExchange(String m, URI u, Request req, int len, HttpConnection connection) throws IOException { this.req = req; this.reqHdrs = req.headers(); this.rspHdrs = new Headers(); this.method = m; this.uri = u; this.connection = connection; this.reqContentLen = len; /* ros only used for headers, body written directly to stream */ this.ros = req.outputStream(); this.ris = req.inputStream(); server = getServerImpl(); server.startExchange(); } public Headers getRequestHeaders() { return new UnmodifiableHeaders(reqHdrs); } public Headers getResponseHeaders() { return rspHdrs; } public URI getRequestURI() { return uri; } public String getRequestMethod() { return method; } public HttpContext getHttpContext() { return connection.getHttpContext(); } public void close() { if (closed) { return; } closed = true; /* close the underlying connection if, * a) the streams not set up yet, no response can be sent, or * b) if the wrapper output stream is not set up, or * c) if the close of the input/outpu stream fails */ try { if (uis_orig == null || uos == null) { connection.close(); return; } if (!uos_orig.isWrapped()) { connection.close(); return; } if (!uis_orig.isClosed()) { uis_orig.close(); } uos.close(); } catch (IOException e) { connection.close(); } } public InputStream getRequestBody() { if (uis != null) { return uis; } if (reqContentLen == -1) { uis_orig = new ChunkedInputStream(this, ris); uis = uis_orig; } else { uis_orig = new FixedLengthInputStream(this, ris, reqContentLen); uis = uis_orig; } return uis; } LeftOverInputStream getOriginalInputStream() { return uis_orig; } public int getResponseCode() { return rcode; } public OutputStream getResponseBody() { /* TODO. Change spec to remove restriction below. Filters * cannot work with this restriction * * if (!sentHeaders) { * throw new IllegalStateException ("headers not sent"); * } */ if (uos == null) { uos_orig = new PlaceholderOutputStream(null); uos = uos_orig; } return uos; } /* returns the place holder stream, which is the stream * returned from the 1st call to getResponseBody() * The "real" ouputstream is then placed inside this */ PlaceholderOutputStream getPlaceholderResponseBody() { getResponseBody(); return uos_orig; } public void sendResponseHeaders(int rCode, long contentLen) throws IOException { if (sentHeaders) { throw new IOException("headers already sent"); } this.rcode = rCode; String statusLine = "HTTP/1.1 " + rCode + Code.msg(rCode) + "\r\n"; OutputStream tmpout = new BufferedOutputStream(ros); PlaceholderOutputStream o = getPlaceholderResponseBody(); tmpout.write(bytes(statusLine, 0), 0, statusLine.length()); boolean noContentToSend = false; // assume there is content rspHdrs.set("Date", df.format(new Date())); if (contentLen == 0) { if (http10) { o.setWrappedStream(new UndefLengthOutputStream(this, ros)); close = true; } else { rspHdrs.set("Transfer-encoding", "chunked"); o.setWrappedStream(new ChunkedOutputStream(this, ros)); } } else { if (contentLen == -1) { noContentToSend = true; contentLen = 0; } /* content len might already be set, eg to implement HEAD resp */ if (rspHdrs.getFirst("Content-length") == null) { rspHdrs.set("Content-length", Long.toString(contentLen)); } o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen)); } write(rspHdrs, tmpout); this.rspContentLen = contentLen; tmpout.flush(); tmpout = null; sentHeaders = true; if (noContentToSend) { WriteFinishedEvent e = new WriteFinishedEvent(this); server.addEvent(e); closed = true; } server.logReply(rCode, req.requestLine(), null); } void write(Headers map, OutputStream os) throws IOException { Set<Map.Entry<String, List<String>>> entries = map.entrySet(); for (Map.Entry<String, List<String>> entry : entries) { String key = entry.getKey(); byte[] buf; List<String> values = entry.getValue(); for (String val : values) { int i = key.length(); buf = bytes(key, 2); buf[i++] = ':'; buf[i++] = ' '; os.write(buf, 0, i); buf = bytes(val, 2); i = val.length(); buf[i++] = '\r'; buf[i++] = '\n'; os.write(buf, 0, i); } } os.write('\r'); os.write('\n'); } private byte[] rspbuf = new byte[128]; // used by bytes() /** * convert string to byte[], using rspbuf * Make sure that at least "extra" bytes are free at end * of rspbuf. Reallocate rspbuf if not big enough. * caller must check return value to see if rspbuf moved */ private byte[] bytes(String s, int extra) { int slen = s.length(); if (slen + extra > rspbuf.length) { int diff = slen + extra - rspbuf.length; rspbuf = new byte[2 * (rspbuf.length + diff)]; } char c[] = s.toCharArray(); for (int i = 0; i < c.length; i++) { rspbuf[i] = (byte) c[i]; } return rspbuf; } public InetSocketAddress getRemoteAddress() { Socket s = connection.getChannel().socket(); InetAddress ia = s.getInetAddress(); int port = s.getPort(); return new InetSocketAddress(ia, port); } public InetSocketAddress getLocalAddress() { Socket s = connection.getChannel().socket(); InetAddress ia = s.getLocalAddress(); int port = s.getLocalPort(); return new InetSocketAddress(ia, port); } public String getProtocol() { String reqline = req.requestLine(); int index = reqline.lastIndexOf(' '); return reqline.substring(index + 1); } public Object getAttribute(String name) { if (name == null) { throw new NullPointerException("null name parameter"); } if (attributes == null) { attributes = getHttpContext().getAttributes(); } return attributes.get(name); } public void setAttribute(String name, Object value) { if (name == null) { throw new NullPointerException("null name parameter"); } if (attributes == null) { attributes = getHttpContext().getAttributes(); } attributes.put(name, value); } public void setStreams(InputStream i, OutputStream o) { assert uis != null; if (i != null) { uis = i; } if (o != null) { uos = o; } } /** * PP */ HttpConnection getConnection() { return connection; } HttpServer getServerImpl() { return getHttpContext().getServer(); } } /** * An OutputStream which wraps another stream * which is supplied either at creation time, or sometime later. * If a caller/user tries to write to this stream before * the wrapped stream has been provided, then an IOException will * be thrown. */ class PlaceholderOutputStream extends java.io.OutputStream { OutputStream wrapped; PlaceholderOutputStream(OutputStream os) { wrapped = os; } void setWrappedStream(OutputStream os) { wrapped = os; } boolean isWrapped() { return wrapped != null; } private void checkWrap() throws IOException { if (wrapped == null) { throw new IOException("response headers not sent yet"); } } public void write(int b) throws IOException { checkWrap(); wrapped.write(b); } public void write(byte b[]) throws IOException { checkWrap(); wrapped.write(b); } public void write(byte b[], int off, int len) throws IOException { checkWrap(); wrapped.write(b, off, len); } public void flush() throws IOException { checkWrap(); wrapped.flush(); } public void close() throws IOException { checkWrap(); wrapped.close(); } }