--- /dev/null
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+/**
+ * Used by socks and http proxy. Will copy received data to a different
+ * channel.
+ */
+public class CopyCallback implements IOConnector.DataReceivedCallback {
+ IOChannel mOutBuffer;
+
+ public CopyCallback(IOChannel sc) {
+ mOutBuffer = sc;
+ }
+
+ @Override
+ public void handleReceived(IOChannel ch) throws IOException {
+ IOBuffer inBuffer = ch.getIn();
+ IOChannel outBuffer = mOutBuffer;
+ if (outBuffer == null &&
+ ch instanceof HttpChannel) {
+ outBuffer =
+ (IOChannel) ((HttpChannel)ch).getRequest().getAttribute("P");
+ }
+ // body.
+ while (true) {
+ if (outBuffer == null || outBuffer.getOut() == null) {
+ return;
+ }
+ if (outBuffer.getOut().isAppendClosed()) {
+ return;
+ }
+
+ ByteBuffer bb = outBuffer.getOut().getWriteBuffer();
+ int rd = inBuffer.read(bb);
+ outBuffer.getOut().releaseWriteBuffer(rd);
+
+ if (rd == 0) {
+ outBuffer.startSending();
+ return;
+ }
+ if (rd < 0) {
+ outBuffer.getOut().close();
+ outBuffer.startSending();
+ return;
+ }
+ }
+ }
+ }
\ No newline at end of file
--- /dev/null
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.MultiMap;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+/**
+ * Http callback for the server-side. Will forward all requests to
+ * a remote http server - either using proxy mode ( GET http://... ) or
+ * forward requests ( GET /foo -> will be served by the remote server ).
+ *
+ * This is not blocking (except the connect, which currenly blocks on dns).
+ */
+public class HttpProxyService implements HttpService {
+
+ // target - when used in forwarding mode.
+ String target = "localhost";
+ int port = 8802;
+
+ static Logger log = Logger.getLogger("HttpProxy");
+ public static boolean debug = false;
+ boolean keepOpen = true;
+
+ // client side - this connect to the real server that generates the resp.
+ ProxyClientCallback clientHeadersReceived = new ProxyClientCallback();
+
+ HttpConnector httpConnector;
+ IOConnector ioConnector;
+
+ public HttpProxyService withSelector(IOConnector pool) {
+ this.ioConnector = pool;
+ return this;
+ }
+
+ public HttpProxyService withHttpClient(HttpConnector pool) {
+ this.httpConnector = pool;
+ return this;
+ }
+
+ public HttpProxyService withTarget(String host, int port) {
+ this.target = host;
+ this.port = port;
+ return this;
+ }
+
+ private IOConnector getSelector() {
+ if (ioConnector == null) {
+ ioConnector = new SocketConnector();
+ }
+ return ioConnector;
+ }
+
+ private HttpConnector getHttpConnector() {
+ if (httpConnector == null) {
+ httpConnector = new HttpConnector(getSelector());
+ }
+ return httpConnector;
+ }
+
+ // Connects to the target CONNECT server, as client, forwards
+ static class ProxyConnectClientConnection implements IOConnector.ConnectedCallback {
+
+ IOChannel serverNet;
+ private HttpChannel serverHttp;
+
+ public ProxyConnectClientConnection(HttpChannel sproc) throws IOException {
+ this.serverNet = sproc.getSink();
+ this.serverHttp = sproc;
+ }
+
+ @Override
+ public void handleConnected(IOChannel ioch) throws IOException {
+ if (!ioch.isOpen()) {
+ serverNet.close();
+ log.severe("Connection failed");
+ return;
+ }
+ afterClientConnect(ioch);
+
+ ioch.setDataReceivedCallback(new CopyCallback(serverNet));
+ //ioch.setDataFlushedCallback(new ProxyFlushedCallback(serverNet, ioch));
+ serverNet.setDataReceivedCallback(new CopyCallback(ioch));
+ //serverNet.setDataFlushedCallback(new ProxyFlushedCallback(ioch, serverNet));
+
+ ioch.sendHandleReceivedCallback();
+ }
+
+ static byte[] OK = "HTTP/1.1 200 OK\r\n\r\n".getBytes();
+
+ protected void afterClientConnect(IOChannel clientCh) throws IOException {
+ serverNet.getOut().queue(OK);
+ serverNet.startSending();
+
+ serverHttp.resetBuffers(); // no buffers
+ serverHttp.release(); // no longer used
+ }
+ }
+
+ /**
+ * Parse the req, dispatch the connection.
+ */
+ @Override
+ public void service(HttpRequest serverHttpReq, HttpResponse serverHttpRes)
+ throws IOException {
+
+ String dstHost = target; // default target ( for normal req ).
+ int dstPort = port;
+
+ // TODO: more flexibility/callbacks on selecting the target, acl, etc
+ if (serverHttpReq.method().equals("CONNECT")) {
+ // SSL proxy - just connect and forward all packets
+ // TODO: optimize, add error checking
+ String[] hostPort = serverHttpReq.requestURI().toString().split(":");
+ String host = hostPort[0];
+ int port = 443;
+ if (hostPort.length > 1) {
+ port = Integer.parseInt(hostPort[1]);
+ }
+ if (log.isLoggable(Level.FINE)) {
+ HttpChannel server = serverHttpReq.getHttpChannel();
+ log.info("NEW: " + server.getId() + " " + dstHost + " " +
+ server.getRequest().getMethod() +
+ " " + server.getRequest().getRequestURI() + " " +
+ server.getIn());
+ }
+
+ try {
+ getSelector().connect(host, port,
+ new ProxyConnectClientConnection(serverHttpReq.getHttpChannel()));
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return;
+ }
+
+
+ CBuffer origURIx = serverHttpReq.requestURI();
+ String origURI = origURIx.toString();
+ if (origURI.startsWith("http://")) {
+ // Real proxy - extract client address, modify the uri.
+ // TODO: optimize the strings.
+ int start = origURI.indexOf('/', 7);
+ String hostPortS = (start == -1) ?
+ origURI.subSequence(7, origURI.length()).toString() :
+ origURI.subSequence(7, start).toString();
+ String[] hostPort = hostPortS.split(":");
+
+ dstHost = hostPort[0];
+ dstPort = (hostPort.length > 1) ? Integer.parseInt(hostPort[1]) :
+ 80;
+
+ if (start >= 0) {
+ serverHttpReq.requestURI().set(origURI.substring(start));
+ } else {
+ serverHttpReq.requestURI().set("/");
+ }
+ } else {
+ // Adjust the host header.
+ CBuffer hostHdr =
+ serverHttpReq.getMimeHeaders().getHeader("host");
+ if (hostHdr != null) {
+ hostHdr.recycle();
+ CBuffer cb = hostHdr;
+ cb.append(dstHost);
+ if (dstPort != 80) {
+ cb.append(':');
+ cb.appendInt(dstPort);
+ }
+ }
+ }
+ if (debug) {
+ HttpChannel server = serverHttpReq.getHttpChannel();
+ log.info("START: " + server.getId() + " " + dstHost + " " +
+ server.getRequest().getMethod() +
+ " " + server.getRequest().getRequestURI() + " " +
+ server.getIn());
+ }
+
+ // Send the request with a non-blocking write
+ HttpChannel serverHttp = serverHttpReq.getHttpChannel();
+
+ // Client connection
+ HttpChannel httpClient = getHttpConnector().get(dstHost, dstPort);
+
+ serverHttp.getRequest().setAttribute("CLIENT", httpClient);
+ httpClient.getRequest().setAttribute("SERVER", serverHttp);
+ serverHttp.getRequest().setAttribute("P", httpClient);
+ httpClient.getRequest().setAttribute("P", serverHttp);
+
+ httpClient.setHttpService(clientHeadersReceived);
+
+ // Will send the original request (TODO: small changes)
+ // Response is not affected ( we use the callback )
+ httpClient.getRequest().method().set(serverHttp.getRequest().method());
+ httpClient.getRequest().requestURI().set(serverHttp.getRequest().requestURI());
+ if (serverHttp.getRequest().queryString().length() != 0) {
+ httpClient.getRequest().queryString().set(serverHttp.getRequest().queryString());
+ }
+
+ httpClient.getRequest().protocol().set(serverHttp.getRequest().protocol());
+
+ //cstate.reqHeaders.addValue(name)
+ copyHeaders(serverHttp.getRequest().getMimeHeaders(),
+ httpClient.getRequest().getMimeHeaders() /*dest*/);
+
+ // For debug
+ httpClient.getRequest().getMimeHeaders().remove("Accept-Encoding");
+
+ if (!keepOpen) {
+ httpClient.getRequest().getMimeHeaders().setValue("Connection").set("Close");
+ }
+
+ // Any data
+ serverHttp.setDataReceivedCallback(copy);
+ copy.handleReceived(serverHttp);
+
+ httpClient.sendRequest();
+
+
+ //serverHttp.handleReceived(serverHttp.getSink());
+ //httpClient.flush(); // send any data still there
+
+ httpClient.setCompletedCallback(done);
+ // Will call release()
+ serverHttp.setCompletedCallback(done);
+
+ serverHttpReq.async();
+ }
+
+ static HttpDoneCallback done = new HttpDoneCallback();
+ static CopyCallback copy = new CopyCallback(null);
+ // POST: after sendRequest(ch) we need to forward the body !!
+
+
+ static void copyHeaders(MultiMap mimeHeaders, MultiMap dest)
+ throws IOException {
+ for (int i = 0; i < mimeHeaders.size(); i++) {
+ CBuffer name = mimeHeaders.getName(i);
+ CBuffer val = dest.addValue(name.toString());
+ val.set(mimeHeaders.getValue(i));
+ }
+ }
+
+ /**
+ * HTTP _CLIENT_ callback - from tomcat to final target.
+ */
+ public class ProxyClientCallback implements HttpService {
+ /**
+ * Headers received from the client (content http server).
+ * TODO: deal with version missmatches.
+ */
+ @Override
+ public void service(HttpRequest clientHttpReq, HttpResponse clientHttpRes) throws IOException {
+ HttpChannel serverHttp = (HttpChannel) clientHttpReq.getAttribute("SERVER");
+
+ try {
+ serverHttp.getResponse().setStatus(clientHttpRes.getStatus());
+ serverHttp.getResponse().getMessageBuffer().set(clientHttpRes.getMessageBuffer());
+ copyHeaders(clientHttpRes.getMimeHeaders(),
+ serverHttp.getResponse().getMimeHeaders());
+
+ serverHttp.getResponse().getMimeHeaders().addValue("TomcatProxy").set("True");
+
+ clientHttpReq.getHttpChannel().setDataReceivedCallback(copy);
+ copy.handleReceived(clientHttpReq.getHttpChannel());
+
+ serverHttp.sendHeaders();
+ serverHttp.startSending();
+
+
+ //clientHttpReq.flush(); // send any data still there
+
+ // if (clientHttpReq.getHttpChannel().getIn().isClosedAndEmpty()) {
+ // serverHttp.getOut().close(); // all data from client already in buffers
+ // }
+
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ static final class HttpDoneCallback implements RequestCompleted {
+
+ public HttpDoneCallback() {
+ }
+
+ @Override
+ public void handle(HttpChannel doneCh, Object extraData) throws IOException {
+ HttpChannel serverCh =
+ (HttpChannel) doneCh.getRequest().getAttribute("SERVER");
+ HttpChannel clientCh = doneCh;
+ String tgt = "C";
+ if (serverCh == null) {
+ serverCh = doneCh;
+ clientCh =
+ (HttpChannel) doneCh.getRequest().getAttribute("CLIENT");
+ tgt = "S";
+ }
+ if (serverCh == null || clientCh == null) {
+ return;
+ }
+ if (doneCh.getError()) {
+ serverCh.abort("Proxy error");
+ clientCh.abort("Proxy error");
+ return;
+ }
+
+ if (log.isLoggable(Level.FINE)) {
+ HttpChannel peerCh =
+ (HttpChannel) doneCh.getRequest().getAttribute("SERVER");
+ if (peerCh == null) {
+ peerCh =
+ (HttpChannel) doneCh.getRequest().getAttribute("CLIENT");
+ } else {
+
+ }
+ log.info(tgt + " " + peerCh.getId() + " " +
+ doneCh.getTarget() + " " +
+ doneCh.getRequest().getMethod() +
+ " " + doneCh.getRequest().getRequestURI() + " " +
+ doneCh.getResponse().getStatus() + " IN:" + doneCh.getIn()
+ + " OUT:" + doneCh.getOut() +
+ " SIN:" + peerCh.getIn() +
+ " SOUT:" + peerCh.getOut() );
+ }
+ // stop forwarding. After this call the client object will be
+ // recycled
+ //clientCB.outBuffer = null;
+
+ // We must releaes both at same time
+ synchronized (this) {
+
+ serverCh.complete();
+
+ if (clientCh.getRequest().getAttribute("SERVER") == null) {
+ return;
+ }
+ if (clientCh.isDone() && serverCh.isDone()) {
+ clientCh.getRequest().setAttribute("SERVER", null);
+ serverCh.getRequest().setAttribute("CLIENT", null);
+ clientCh.getRequest().setAttribute("P", null);
+ serverCh.getRequest().setAttribute("P", null);
+ // Reuse the objects.
+ serverCh.release();
+ clientCh.release();
+ }
+ }
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+public final class ProxyFlushedCallback implements IOConnector.DataFlushedCallback {
+ IOChannel peerCh;
+
+ public ProxyFlushedCallback(IOChannel ch2, IOChannel clientChannel2) {
+ peerCh = ch2;
+ }
+
+ @Override
+ public void handleFlushed(IOChannel ch) throws IOException {
+ if (ch.getOut().isClosedAndEmpty()) {
+ if (!peerCh.getOut().isAppendClosed()) {
+ peerCh.close();
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+/**
+ * A test for the selector package, and helper for the proxy -
+ * a SOCKS4a server.
+ *
+ * Besides the connection initialization, it's almost the
+ * same as the CONNECT method in http proxy.
+ *
+ * http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
+ * http://www.smartftp.com/Products/SmartFTP/RFC/socks4a.protocol
+ * http://www.faqs.org/rfcs/rfc1928.html
+ * https://svn.torproject.org/svn/tor/trunk/doc/spec/socks-extensions.txt
+ *
+ * In firefox, set network.proxy.socks_remote_dns = true to do DNS via proxy.
+ *
+ * Also interesting:
+ * http://transocks.sourceforge.net/
+ *
+ * @author Costin Manolache
+ */
+public class SocksServer implements Runnable, IOConnector.ConnectedCallback {
+ protected int port = 2080;
+
+ protected IOConnector ioConnector;
+ protected static Logger log = Logger.getLogger("SocksServer");
+
+ protected long idleTimeout = 10 * 60000; // 10 min
+
+ protected long lastConnection = 0;
+ protected long totalConTime = 0;
+ protected AtomicInteger totalConnections = new AtomicInteger();
+
+ protected AtomicInteger active = new AtomicInteger();
+
+ protected long inBytes;
+ protected long outBytes;
+ protected static int sockets;
+
+ public int getPort() {
+ return port;
+ }
+
+ public int getActive() {
+ return active.get();
+ }
+
+ public int getTotal() {
+ return totalConnections.get();
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public void handleAccepted(IOChannel accepted) throws IOException {
+ lastConnection = System.currentTimeMillis();
+ active.incrementAndGet();
+ totalConnections.incrementAndGet();
+ sockets++;
+
+ final SocksServerConnection socksCon = new SocksServerConnection(accepted);
+ socksCon.pool = ioConnector;
+ socksCon.server = this;
+
+ accepted.setDataReceivedCallback(socksCon);
+ socksCon.handleReceived(accepted);
+ }
+
+ /**
+ * Exit if no activity happens.
+ */
+ public void setIdleTimeout(long to) {
+ idleTimeout = to;
+ }
+
+ public long getIdleTimeout() {
+ return idleTimeout;
+ }
+
+ public void stop() {
+ ioConnector.stop();
+ }
+
+ public void initServer() throws IOException {
+ if (ioConnector == null) {
+ ioConnector = new SocketConnector();
+ }
+ ioConnector.acceptor(this, Integer.toString(port), null);
+
+ final Timer timer = new Timer(true /* daemon */);
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ // if lastConnection == 0 - it'll terminate on first timer
+ float avg = (totalConnections.get() > 0) ?
+ totalConTime / totalConnections.get() : 0;
+ System.err.println("Socks:"
+ + "\ttotal=" + totalConnections
+ + "\tin=" + inBytes
+ + "\tout=" + outBytes
+ + "\tavg=" + (int) avg);
+ if (active.get() <= 0
+ && idleTimeout > 0
+ && System.currentTimeMillis() - lastConnection > idleTimeout) {
+ System.err.println("Idle timeout");
+ stop();
+ this.cancel();
+ timer.cancel();
+ }
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Error in timer", t);
+ }
+ }
+ }, 5 * 60 * 1000, 5 * 60 * 1000); // 5
+
+
+ }
+
+
+
+ public static class SocksServerConnection implements IOConnector.DataReceivedCallback, IOConnector.ConnectedCallback {
+
+ protected SocksServer server;
+
+ boolean headReceived;
+ boolean head5Received = false;
+
+ ByteBuffer headBuffer = ByteBuffer.allocate(256);
+ ByteBuffer headReadBuffer = headBuffer.duplicate();
+
+ ByteBuffer headResBuffer = ByteBuffer.allocate(256);
+ IOConnector pool;
+ byte ver;
+ byte cmd;
+ long startTime = System.currentTimeMillis();
+
+ static final int CMD_CONNECT = 0;
+ static final byte CMD_RESOLVE = (byte) 0xF0;
+
+ int port;
+ byte[] hostB = new byte[4];
+ CharBuffer userId = CharBuffer.allocate(256);
+ CharBuffer hostName = CharBuffer.allocate(256);
+
+ SocketAddress sa = null;
+
+ private byte atyp;
+
+ IOChannel serverCh;
+
+ public SocksServerConnection(IOChannel accepted) {
+ this.serverCh = accepted;
+ }
+
+ protected void afterClientConnect(IOChannel clientCh) throws IOException {
+ headResBuffer.clear();
+ if (ver == 4) {
+ headResBuffer.put((byte) 0);
+ headResBuffer.put((byte) 90);
+ for (int i = 0; i < 6; i++ ) {
+ headResBuffer.put((byte) 0);
+ }
+ } else {
+ headResBuffer.put((byte) 5);
+ headResBuffer.put((byte) 0);
+ headResBuffer.put((byte) 0);
+ headResBuffer.put((byte) 1); // ip
+
+ headResBuffer.put(hostB);
+ int port2 = clientCh.getPort(true);
+ headResBuffer.putShort((short) port2);
+ }
+
+ headResBuffer.flip();
+
+ serverCh.getOut().queue(headResBuffer);
+ log.fine("Connected " + sa.toString());
+
+ if (headReadBuffer.remaining() > 0) {
+ serverCh.getOut().queue(headReadBuffer);
+ }
+ serverCh.startSending();
+ }
+
+ public void afterClose() {
+ long conTime = System.currentTimeMillis() - startTime;
+ int a = server.active.decrementAndGet();
+ if (a < 0) {
+ System.err.println("negative !!");
+ server.active.set(0);
+ }
+// System.err.println(sa + "\tsR:" +
+// received
+// + "\tcR:" + clientReceived
+// + "\tactive:" + a
+// + "\ttotC:" + server.totalConnections
+// + "\ttime:" + conTime);
+// server.inBytes += received;
+// server.totalConTime += conTime;
+// server.outBytes += clientReceived;
+ }
+
+
+ protected int parseHead() throws IOException {
+ // data is between 0 and pos.
+ int pos = headBuffer.position();
+ headReadBuffer.clear();
+ headReadBuffer.limit(pos);
+ if (headReadBuffer.remaining() < 2) {
+ return -1;
+ }
+
+ ByteBuffer bb = headReadBuffer;
+ ver = bb.get();
+ if (ver == 5) {
+ return parseHead5();
+ }
+ if (headReadBuffer.remaining() < 8) {
+ return -1;
+ }
+ cmd = bb.get();
+ port = bb.getShort();
+ bb.get(hostB);
+ userId.clear();
+ int rc = readStringZ(bb, userId);
+ // Mozilla userid: MOZ ...
+ if (rc == -1) {
+ return rc;
+ }
+ if (hostB[0] == 0 && hostB[1] == 0 && hostB[2] == 0) {
+ // 0.0.0.x
+ atyp = 3;
+ hostName.clear();
+ rc = readStringZ(bb, hostName);
+ if (rc == -1) {
+ return rc;
+ }
+ } else {
+ atyp = 1;
+ }
+
+ headReceived = true;
+
+ return 4;
+ }
+
+ protected int parseHead5_2() throws IOException {
+ // data is between 0 and pos.
+ int pos = headBuffer.position();
+
+ headReadBuffer.clear();
+ headReadBuffer.limit(pos);
+
+ if (headReadBuffer.remaining() < 7) {
+ return -1;
+ }
+
+ ByteBuffer bb = headReadBuffer;
+ ver = bb.get();
+ cmd = bb.get();
+ bb.get(); // reserved
+ atyp = bb.get();
+ if (atyp == 1) {
+ bb.get(hostB);
+ } else if (atyp == 3) {
+ hostName.clear();
+ int rc = readStringN(bb, hostName);
+ if (rc == -1) {
+ return rc;
+ }
+ } // ip6 not supported right now, easy to add
+
+ port = bb.getShort();
+
+ head5Received = true;
+
+ return 5;
+ }
+
+ private int parseHead5() {
+ ByteBuffer bb = headReadBuffer;
+ int nrMethods = ((int)bb.get()) & 0xFF;
+ if (bb.remaining() < nrMethods) {
+ return -1;
+ }
+ for (int i = 0; i < nrMethods; i++) {
+ // ignore
+ bb.get();
+ }
+ return 5;
+ }
+
+ private int readStringZ(ByteBuffer bb, CharBuffer bc) throws IOException {
+ bc.clear();
+ while (true) {
+ if (!bb.hasRemaining()) {
+ return -1; // not complete
+ }
+ byte b = bb.get();
+ if (b == 0) {
+ bc.flip();
+ return 0;
+ } else {
+ bc.put((char) b);
+ }
+ }
+ }
+
+ private int readStringN(ByteBuffer bb, CharBuffer bc) throws IOException {
+ bc.clear();
+ int len = ((int) bb.get()) & 0xff;
+ for (int i = 0; i < len; i++) {
+ if (!bb.hasRemaining()) {
+ return -1; // not complete
+ }
+ byte b = bb.get();
+ bc.put((char) b);
+ }
+ bc.flip();
+ return len;
+ }
+
+ static ExecutorService connectTP = Executors.newCachedThreadPool();
+
+ protected void startClientConnection() throws IOException {
+ // TODO: use different thread ?
+ if (atyp == 3) {
+ connectTP.execute(new Runnable() {
+
+ public void run() {
+ try {
+ sa = new InetSocketAddress(hostName.toString(), port);
+ pool.connect(hostName.toString(), port,
+ SocksServerConnection.this);
+ } catch (Exception ex) {
+ log.severe("Error connecting");
+ }
+ }
+ });
+ } else {
+ InetAddress addr = InetAddress.getByAddress(hostB);
+ pool.connect(addr.toString(), port, this);
+ } // TODO: ip6
+ }
+
+ public void handleConnected(IOChannel ioch) throws IOException {
+ ioch.setDataReceivedCallback(new CopyCallback(serverCh));
+ //ioch.setDataFlushedCallback(new ProxyFlushedCallback(serverCh, ioch));
+
+ serverCh.setDataReceivedCallback(new CopyCallback(ioch));
+ //serverCh.setDataFlushedCallback(new ProxyFlushedCallback(ioch, serverCh));
+
+ afterClientConnect(ioch);
+
+ ioch.sendHandleReceivedCallback();
+ }
+
+
+ @Override
+ public void handleReceived(IOChannel net) throws IOException {
+ IOBuffer ch = net.getIn();
+ //SelectorChannel ch = (SelectorChannel) ioch;
+ if (!headReceived) {
+ int rd = ch.read(headBuffer);
+ if (rd == 0) {
+ return;
+ }
+ if (rd == -1) {
+ ch.close();
+ }
+
+ rd = parseHead();
+ if (rd < 0) {
+ return; // need more
+ }
+ if (rd == 5) {
+ headResBuffer.clear();
+ headResBuffer.put((byte) 5);
+ headResBuffer.put((byte) 0);
+ headResBuffer.flip();
+ net.getOut().queue(headResBuffer);
+ net.startSending();
+ headReceived = true;
+ headBuffer.clear();
+ return;
+ } else {
+ headReceived = true;
+ head5Received = true;
+ startClientConnection();
+ }
+ }
+
+ if (!head5Received) {
+ int rd = ch.read(headBuffer);
+ if (rd == 0) {
+ return;
+ }
+ if (rd == -1) {
+ ch.close();
+ }
+
+ rd = parseHead5_2();
+ if (rd < 0) {
+ return; // need more
+ }
+
+ startClientConnection();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ initServer();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void handleConnected(IOChannel ch) throws IOException {
+ handleAccepted(ch);
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.IOBuffer;
+
+/*
+ *
+ * Serve static content, from memory.
+ */
+public class StaticContentService implements HttpService {
+ protected Logger log = Logger.getLogger("coyote.static");
+ protected BBucket mb;
+
+ protected boolean chunked = false;
+
+ protected String contentType = "text/plain";
+
+
+ public StaticContentService() {
+ }
+
+ public StaticContentService chunked() {
+ chunked = true;
+ return this;
+ }
+
+ public StaticContentService setData(byte[] data) {
+ mb = BBuffer.wrapper(data, 0, data.length);
+ return this;
+ }
+
+ public StaticContentService withLen(int len) {
+ byte[] data = new byte[len];
+ for (int i = 0; i < len; i++) {
+ data[i] = 'A';
+ }
+ mb = BBuffer.wrapper(data, 0, data.length);
+ return this;
+ }
+
+
+ public StaticContentService setData(CharSequence data) {
+ try {
+ IOBuffer tmp = new IOBuffer(null);
+ tmp.append(data);
+ mb = tmp.readAll(null);
+ } catch (IOException e) {
+ }
+ return this;
+ }
+
+ public StaticContentService setContentType(String ct) {
+ this.contentType = ct;
+ return this;
+ }
+
+ public void setFile(String path) {
+ try {
+ FileInputStream fis = new FileInputStream(path);
+ BBuffer bc = BBuffer.allocate(4096);
+
+ byte b[] = new byte[4096];
+ int rd = 0;
+ while ((rd = fis.read(b)) > 0) {
+ bc.append(b, 0, rd);
+ }
+ mb = bc;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void service(HttpRequest httpReq, HttpResponse res) throws IOException {
+
+ res.setStatus(200);
+
+ if (!chunked) {
+ res.setContentLength(mb.remaining());
+ }
+ res.setContentType(contentType);
+
+ res.sendHead();
+
+ if (chunked) {
+ res.getBody()
+ .queue(BBuffer.wrapper(mb, 0, mb.remaining()));
+ res.flush();
+ }
+
+ res.getBody().queue(BBuffer.wrapper(mb, 0, mb.remaining()));
+ }
+}
\ No newline at end of file