diff options
Diffstat (limited to 'packages/services')
-rw-r--r-- | packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java | 202 |
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) { |