summaryrefslogtreecommitdiffstats
path: root/packages/services
diff options
context:
space:
mode:
authorAndrei Kapishnikov <kapishnikov@google.com>2014-12-19 16:01:15 -0500
committerAndrei Kapishnikov <kapishnikov@google.com>2015-02-11 14:58:31 -0500
commit1e64ab3e56f6efafc25301df5da21d69bb75b470 (patch)
tree9418997afaa58c4dcf8358b1e5ae2b8c2d8af409 /packages/services
parentd08860690c1aa087871e14c841524e7ce3c0a4f9 (diff)
downloadframeworks_base-1e64ab3e56f6efafc25301df5da21d69bb75b470.zip
frameworks_base-1e64ab3e56f6efafc25301df5da21d69bb75b470.tar.gz
frameworks_base-1e64ab3e56f6efafc25301df5da21d69bb75b470.tar.bz2
Replace absolute_uri with absolute_path when HTTP request is forwarded to non-proxy server.
According to the spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html): When Request-URI is used to identify a resource on an origin server or gateway, the absolute path of the URI MUST be transmitted as the Request-URI. Before the change,the proxy sent absolute_uri, which is used to send HTTP requests to proxies: “The absoluteURI form is REQUIRED when the request is being made to a proxy.” Related Bug 18776631 Other changes: 1. Remove proxy-connection header when the request is sent to an origin server. 2. Added “connection = close” header to indicate that the origin server needs to close the connection right after the response. Currently, our proxy does not support keep-alive (persistent) connections because it analyses only the first request for a given connection and; therefore, cannot perform required request content substitutions. 3. Fixed an issue when a non-numeric host port number resulted in forwarding requests to default 443 port for SSL connections, e.g. request to https://google.com:ZZZ, would be translated to https://google.com:443. 4. Fixed an issue when the proxy tries to establish direct connection to the origin server even when it is not supposed to do that according to PAC. That happened when PAC returned a proxy server that is not available. 5. Prevent ProxyServer from crashing when PacService throws an exception by intercepting all exception types. Change-Id: I7d50b13b4cb24199a690a53c1925e87c15a8d45b
Diffstat (limited to 'packages/services')
-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) {