/* This file is part of "MidpSSH".
* Copyright (c) 2006 Karl von Randow.
*
* --LICENSE NOTICE--
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* --LICENSE NOTICE--
*
*/
package midpssh;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class Server implements Runnable {
private static final String HTTP_HEADERS = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-type: application/octet-stream\r\n";
private static final String CRLF = "\r\n";
private static Map current;
private Socket clientSocket;
private HttpInputStream in;
private HttpOutputStream out;
private Session session;
private String key;
private String host;
private int port;
/**
* The application's main method. Starts listening on a socket and creates a new instance
* of the Server class for each socket connection accepted.
*/
public static void main(String[] args) throws Exception {
current = new HashMap();
int port = 8088;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
System.out.println("Starting MidpSSH HTTP Proxy on port " + port);
ServerSocket ss = new ServerSocket(port);
while (true) {
Socket s = ss.accept();
new Server(s);
}
}
public Server(Socket socket) {
this.clientSocket = socket;
new Thread(this).start();
}
/**
* The main server loop. A new thread is created for each socket handled by the server. This
* method handles that socket and exists until the socket is closed.
*/
public void run() {
debug("Connection from " + clientSocket);
try {
in = new HttpInputStream(clientSocket.getInputStream());
out = new HttpOutputStream(clientSocket.getOutputStream());
// boolean keptAlive = false;
while (true) {
// if (keptAlive) {
// System.out.println("trying a keep alive read");
// }
HttpFields fields = new HttpFields();
fields.read(in);
// if (keptAlive) {
// System.out.println("kept alive!");
// }
String firstLine = fields.getFirstLine();
if (firstLine != null) {
StringTokenizer tok = new StringTokenizer(firstLine, " ");
String method = tok.nextToken();
String path = tok.nextToken();
/* Determine the key that uniquely identifies the connection */
key = path;
int i = key.indexOf('/', 1);
host = key.substring(i + 1);
i = host.indexOf(':');
port = Integer.parseInt(host.substring(i + 1));
host = host.substring(0, i);
/* Look for that connection in our current list and use that if possible. Otherwise this
* is a new connection.
*/
synchronized (current) {
if (current.containsKey(key)) {
session = (Session) current.get(key);
session.setLastAccessed(System.currentTimeMillis());
} else {
session = new Session();
current.put(key, session);
}
}
synchronized (session) {
if (!session.isConnected()) {
session.connect(host, port);
}
}
try {
if (method.equalsIgnoreCase("POST")) {
doClientOutbound(fields);
} else {
doClientInbound(fields);
}
}
catch (IOException e) {
/* Remove this connection from the current list */
current.remove(key);
session.close();
throw e;
}
// keptAlive = true;
break;
} else {
break;
}
}
in.close();
out.close();
} catch (IOException e) {
System.out.println(e);
}
try {
clientSocket.close();
} catch (IOException e) {
System.out.println(e);
}
}
/**
* Handle the outbound stream from MidpSSH. Can support chunked POST data, in which case MidpSSH may
* be able to send all outbound data over a single chunked POST. Otherwise multiple and separate POSTs
* are sent.
*
* We read all the data from the post and write it out to the destination server.
* @param fields
* @throws IOException
*/
public void doClientOutbound(HttpFields fields) throws IOException {
OutputStream destout = session.getSocket().getOutputStream();
InputStream in = this.in;
boolean chunking = fields.getField("Transfer-Encoding") != null && fields.getField("Transfer-Encoding").equalsIgnoreCase("chunked");
if (chunking) {
in = new ChunkedInputStream(in);
} else {
int contentLength = fields.getIntField("Content-length");
in = new FiniteInputStream(in, contentLength);
}
byte[] buf = new byte[8192];
int read = in.read(buf);
while (read != -1) {
destout.write(buf, 0, read);
destout.flush();
read = in.read(buf);
}
out.write(HTTP_HEADERS + CRLF);
out.flush();
}
/**
* Handle the inbound stream to MidpSSH. Can support persistent connection to MidpSSH if the phone + network
* can support it: the entire inbound stream is sent during one connection. Otherwise we send back what we have
* and then close the connection.
* @param fields
* @throws IOException
*/
public void doClientInbound(HttpFields fields) throws IOException {
InputStream destin = session.getSocket().getInputStream();
boolean persistent = fields.get("X-MidpSSH-Persistent") != null;
boolean headers = false;
byte[] buf = new byte[8192];
int read = destin.read(buf);
while (read != -1) {
if (!headers) {
if (persistent) {
out.write(HTTP_HEADERS + CRLF);
} else {
out.write(HTTP_HEADERS + "Content-length: " + read + CRLF + CRLF);
}
out.flush();
headers = true;
}
out.write(buf, 0, read);
out.flush();
if (persistent) {
read = destin.read(buf);
} else {
break;
}
}
if (read == -1) {
throw new EOFException("Remote server has closed connection");
}
}
// private String toHex(byte[] ray, int offset, int length) {
// StringBuffer buf = new StringBuffer();
// for (int i = offset; i < offset + length; i++) {
// buf.append("0x");
// buf.append(Integer.toHexString(ray[i]));
// buf.append(' ');
// }
// buf.append('\n');
// buf.append(new String(ray, offset, length));
// return buf.toString();
// }
private void debug(String message) {
System.out.println(message);
}
}