summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java202
1 files changed, 169 insertions, 33 deletions
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index edb1630..ac40222 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -20,6 +20,7 @@ import android.util.Log;
import com.android.net.IProxyPortListener;
import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
import java.io.IOException;
import java.io.InputStream;
@@ -33,6 +34,7 @@ import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -46,6 +48,10 @@ public class ProxyServer extends Thread {
private static final String TAG = "ProxyServer";
+ // HTTP Headers
+ private static final String HEADER_CONNECTION = "connection";
+ private static final String HEADER_PROXY_CONNECTION = "proxy-connection";
+
private ExecutorService threadExecutor;
public boolean mIsRunning = false;
@@ -65,10 +71,6 @@ public class ProxyServer extends Thread {
public void run() {
try {
String requestLine = getLine(connection.getInputStream());
- if (requestLine == null) {
- connection.close();
- return;
- }
String[] splitLine = requestLine.split(" ");
if (splitLine.length < 3) {
connection.close();
@@ -76,22 +78,30 @@ public class ProxyServer extends Thread {
}
String requestType = splitLine[0];
String urlString = splitLine[1];
+ String httpVersion = splitLine[2];
- String host = "";
- int port = 80;
+ URI url = null;
+ String host;
+ int port;
if (requestType.equals(CONNECT)) {
String[] hostPortSplit = urlString.split(":");
host = hostPortSplit[0];
- try {
- port = Integer.parseInt(hostPortSplit[1]);
- } catch (NumberFormatException nfe) {
+ // Use default SSL port if not specified. Parse it otherwise
+ if (hostPortSplit.length < 2) {
port = 443;
+ } else {
+ try {
+ port = Integer.parseInt(hostPortSplit[1]);
+ } catch (NumberFormatException nfe) {
+ connection.close();
+ return;
+ }
}
urlString = "Https://" + host + ":" + port;
} else {
try {
- URI url = new URI(urlString);
+ url = new URI(urlString);
host = url.getHost();
port = url.getPort();
if (port < 0) {
@@ -122,44 +132,99 @@ public class ProxyServer extends Thread {
} else {
server = new Socket(host, port);
if (requestType.equals(CONNECT)) {
- while (getLine(connection.getInputStream()).length() != 0);
+ skipToRequestBody(connection);
// No proxy to respond so we must.
sendLine(connection, HTTP_OK);
} else {
- sendLine(server, requestLine);
+ // Proxying the request directly to the origin server.
+ sendAugmentedRequestToHost(connection, server,
+ requestType, url, httpVersion);
}
}
} catch (IOException ioe) {
-
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to connect to proxy " + proxy, ioe);
+ }
}
if (server != null) {
break;
}
}
- if (server == null) {
+ if (list.isEmpty()) {
server = new Socket(host, port);
if (requestType.equals(CONNECT)) {
- while (getLine(connection.getInputStream()).length() != 0);
+ skipToRequestBody(connection);
// No proxy to respond so we must.
sendLine(connection, HTTP_OK);
} else {
- sendLine(server, requestLine);
+ // Proxying the request directly to the origin server.
+ sendAugmentedRequestToHost(connection, server,
+ requestType, url, httpVersion);
}
}
// Pass data back and forth until complete.
- SocketConnect.connect(connection, server);
- } catch (IOException e) {
+ if (server != null) {
+ SocketConnect.connect(connection, server);
+ }
+ } catch (Exception e) {
Log.d(TAG, "Problem Proxying", e);
}
try {
connection.close();
} catch (IOException ioe) {
+ // Do nothing
+ }
+ }
+ /**
+ * Sends HTTP request-line (i.e. the first line in the request)
+ * that contains absolute path of a given absolute URI.
+ *
+ * @param server server to send the request to.
+ * @param requestType type of the request, a.k.a. HTTP method.
+ * @param absoluteUri absolute URI which absolute path should be extracted.
+ * @param httpVersion version of HTTP, e.g. HTTP/1.1.
+ * @throws IOException if the request-line cannot be sent.
+ */
+ private void sendRequestLineWithPath(Socket server, String requestType,
+ URI absoluteUri, String httpVersion) throws IOException {
+
+ String absolutePath = getAbsolutePathFromAbsoluteURI(absoluteUri);
+ String outgoingRequestLine = String.format("%s %s %s",
+ requestType, absolutePath, httpVersion);
+ sendLine(server, outgoingRequestLine);
+ }
+
+ /**
+ * Extracts absolute path form a given URI. E.g., passing
+ * <code>http://google.com:80/execute?query=cat#top</code>
+ * will result in <code>/execute?query=cat#top</code>.
+ *
+ * @param uri URI which absolute path has to be extracted,
+ * @return the absolute path of the URI,
+ */
+ private String getAbsolutePathFromAbsoluteURI(URI uri) {
+ String rawPath = uri.getRawPath();
+ String rawQuery = uri.getRawQuery();
+ String rawFragment = uri.getRawFragment();
+ StringBuilder absolutePath = new StringBuilder();
+
+ if (rawPath != null) {
+ absolutePath.append(rawPath);
+ } else {
+ absolutePath.append("/");
+ }
+ if (rawQuery != null) {
+ absolutePath.append("?").append(rawQuery);
}
+ if (rawFragment != null) {
+ absolutePath.append("#").append(rawFragment);
+ }
+ return absolutePath.toString();
}
private String getLine(InputStream inputStream) throws IOException {
- StringBuffer buffer = new StringBuffer();
+ StringBuilder buffer = new StringBuilder();
int byteBuffer = inputStream.read();
if (byteBuffer < 0) return "";
do {
@@ -179,6 +244,79 @@ public class ProxyServer extends Thread {
os.write('\n');
os.flush();
}
+
+ /**
+ * Reads from socket until an empty line is read which indicates the end of HTTP headers.
+ *
+ * @param socket socket to read from.
+ * @throws IOException if an exception took place during the socket read.
+ */
+ private void skipToRequestBody(Socket socket) throws IOException {
+ while (getLine(socket.getInputStream()).length() != 0);
+ }
+
+ /**
+ * Sends an augmented request to the final host (DIRECT connection).
+ *
+ * @param src socket to read HTTP headers from.The socket current position should point
+ * to the beginning of the HTTP header section.
+ * @param dst socket to write the augmented request to.
+ * @param httpMethod original request http method.
+ * @param uri original request absolute URI.
+ * @param httpVersion original request http version.
+ * @throws IOException if an exception took place during socket reads or writes.
+ */
+ private void sendAugmentedRequestToHost(Socket src, Socket dst,
+ String httpMethod, URI uri, String httpVersion) throws IOException {
+
+ sendRequestLineWithPath(dst, httpMethod, uri, httpVersion);
+ filterAndForwardRequestHeaders(src, dst);
+
+ // Currently the proxy does not support keep-alive connections; therefore,
+ // the proxy has to request the destination server to close the connection
+ // after the destination server sent the response.
+ sendLine(dst, "Connection: close");
+
+ // Sends and empty line that indicates termination of the header section.
+ sendLine(dst, "");
+ }
+
+ /**
+ * Forwards original request headers filtering out the ones that have to be removed.
+ *
+ * @param src source socket that contains original request headers.
+ * @param dst destination socket to send the filtered headers to.
+ * @throws IOException if the data cannot be read from or written to the sockets.
+ */
+ private void filterAndForwardRequestHeaders(Socket src, Socket dst) throws IOException {
+ String line;
+ do {
+ line = getLine(src.getInputStream());
+ if (line.length() > 0 && !shouldRemoveHeaderLine(line)) {
+ sendLine(dst, line);
+ }
+ } while (line.length() > 0);
+ }
+
+ /**
+ * Returns true if a given header line has to be removed from the original request.
+ *
+ * @param line header line that should be analysed.
+ * @return true if the header line should be removed and not forwarded to the destination.
+ */
+ private boolean shouldRemoveHeaderLine(String line) {
+ int colIndex = line.indexOf(":");
+ if (colIndex != -1) {
+ String headerName = line.substring(0, colIndex).trim();
+ if (headerName.regionMatches(true, 0, HEADER_CONNECTION, 0,
+ HEADER_CONNECTION.length())
+ || headerName.regionMatches(true, 0, HEADER_PROXY_CONNECTION,
+ 0, HEADER_PROXY_CONNECTION.length())) {
+ return true;
+ }
+ }
+ return false;
+ }
}
public ProxyServer() {
@@ -192,23 +330,21 @@ public class ProxyServer extends Thread {
try {
serverSocket = new ServerSocket(0);
- if (serverSocket != null) {
- setPort(serverSocket.getLocalPort());
+ setPort(serverSocket.getLocalPort());
- while (mIsRunning) {
- try {
- Socket socket = serverSocket.accept();
- // Only receive local connections.
- if (socket.getInetAddress().isLoopbackAddress()) {
- ProxyConnection parser = new ProxyConnection(socket);
+ while (mIsRunning) {
+ try {
+ Socket socket = serverSocket.accept();
+ // Only receive local connections.
+ if (socket.getInetAddress().isLoopbackAddress()) {
+ ProxyConnection parser = new ProxyConnection(socket);
- threadExecutor.execute(parser);
- } else {
- socket.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
+ threadExecutor.execute(parser);
+ } else {
+ socket.close();
}
+ } catch (IOException e) {
+ e.printStackTrace();
}
}
} catch (SocketException e) {