1da205a749fadb3a87357d9bd607f094c7717764aJason Monk/**
2da205a749fadb3a87357d9bd607f094c7717764aJason Monk * Copyright (c) 2013, The Android Open Source Project
3da205a749fadb3a87357d9bd607f094c7717764aJason Monk *
4da205a749fadb3a87357d9bd607f094c7717764aJason Monk * Licensed under the Apache License, Version 2.0 (the "License");
5da205a749fadb3a87357d9bd607f094c7717764aJason Monk * you may not use this file except in compliance with the License.
6da205a749fadb3a87357d9bd607f094c7717764aJason Monk * You may obtain a copy of the License at
7da205a749fadb3a87357d9bd607f094c7717764aJason Monk *
8da205a749fadb3a87357d9bd607f094c7717764aJason Monk *     http://www.apache.org/licenses/LICENSE-2.0
9da205a749fadb3a87357d9bd607f094c7717764aJason Monk *
10da205a749fadb3a87357d9bd607f094c7717764aJason Monk * Unless required by applicable law or agreed to in writing, software
11da205a749fadb3a87357d9bd607f094c7717764aJason Monk * distributed under the License is distributed on an "AS IS" BASIS,
12da205a749fadb3a87357d9bd607f094c7717764aJason Monk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13da205a749fadb3a87357d9bd607f094c7717764aJason Monk * See the License for the specific language governing permissions and
14da205a749fadb3a87357d9bd607f094c7717764aJason Monk * limitations under the License.
15da205a749fadb3a87357d9bd607f094c7717764aJason Monk */
16602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkpackage com.android.proxyhandler;
17602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
186f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monkimport android.os.RemoteException;
19602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport android.util.Log;
20602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
216f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monkimport com.android.net.IProxyPortListener;
22602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport com.google.android.collect.Lists;
231e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikovimport com.google.android.collect.Sets;
24602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
25602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.io.IOException;
26602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.io.InputStream;
27602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.io.OutputStream;
28602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.InetSocketAddress;
29602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.Proxy;
30602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.ProxySelector;
31602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.ServerSocket;
32602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.Socket;
33602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.SocketException;
34602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.URI;
35602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.net.URISyntaxException;
36602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.util.List;
371e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikovimport java.util.Set;
38602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.util.concurrent.ExecutorService;
39602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkimport java.util.concurrent.Executors;
40602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
41602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk/**
42602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk * @hide
43602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk */
44602b232a06ede86999aa362a12eb28cbc782dc1dJason Monkpublic class ProxyServer extends Thread {
45602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
46602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private static final String CONNECT = "CONNECT";
47602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private static final String HTTP_OK = "HTTP/1.1 200 OK\n";
48602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
49602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private static final String TAG = "ProxyServer";
50602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
511e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov    // HTTP Headers
521e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov    private static final String HEADER_CONNECTION = "connection";
531e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov    private static final String HEADER_PROXY_CONNECTION = "proxy-connection";
541e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
55602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private ExecutorService threadExecutor;
56602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
57602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    public boolean mIsRunning = false;
58602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
59602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private ServerSocket serverSocket;
606f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    private int mPort;
616f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    private IProxyPortListener mCallback;
62602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
63602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    private class ProxyConnection implements Runnable {
64602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        private Socket connection;
65602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
66602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        private ProxyConnection(Socket connection) {
67602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            this.connection = connection;
68602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
69602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
70602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        @Override
71602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        public void run() {
72602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            try {
73602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                String requestLine = getLine(connection.getInputStream());
74602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                String[] splitLine = requestLine.split(" ");
75602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                if (splitLine.length < 3) {
76602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    connection.close();
77602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    return;
78602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
79602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                String requestType = splitLine[0];
80602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                String urlString = splitLine[1];
811e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                String httpVersion = splitLine[2];
82602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
831e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                URI url = null;
841e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                String host;
851e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                int port;
86602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
87602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                if (requestType.equals(CONNECT)) {
88602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    String[] hostPortSplit = urlString.split(":");
89602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    host = hostPortSplit[0];
901e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    // Use default SSL port if not specified. Parse it otherwise
911e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    if (hostPortSplit.length < 2) {
92602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        port = 443;
931e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    } else {
941e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        try {
951e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                            port = Integer.parseInt(hostPortSplit[1]);
961e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        } catch (NumberFormatException nfe) {
971e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                            connection.close();
981e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                            return;
991e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        }
100602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    }
101602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    urlString = "Https://" + host + ":" + port;
102602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                } else {
103602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    try {
1041e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        url = new URI(urlString);
105602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        host = url.getHost();
106602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        port = url.getPort();
107602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        if (port < 0) {
108602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            port = 80;
109602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        }
110602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    } catch (URISyntaxException e) {
111602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        connection.close();
112602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        return;
113602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    }
114602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
115602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
116602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                List<Proxy> list = Lists.newArrayList();
117602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                try {
118602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    list = ProxySelector.getDefault().select(new URI(urlString));
119602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                } catch (URISyntaxException e) {
120602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    e.printStackTrace();
121602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
122602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                Socket server = null;
123602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                for (Proxy proxy : list) {
124602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    try {
125602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        if (!proxy.equals(Proxy.NO_PROXY)) {
126602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            // Only Inets created by PacProxySelector.
127602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            InetSocketAddress inetSocketAddress =
128179d6e8e2067c380d3c1f7a1a26877b81a9240cbJason Monk                                    (InetSocketAddress)proxy.address();
129179d6e8e2067c380d3c1f7a1a26877b81a9240cbJason Monk                            server = new Socket(inetSocketAddress.getHostName(),
130602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                                    inetSocketAddress.getPort());
131602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            sendLine(server, requestLine);
132602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        } else {
133602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            server = new Socket(host, port);
134602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            if (requestType.equals(CONNECT)) {
1351e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                skipToRequestBody(connection);
136602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                                // No proxy to respond so we must.
137602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                                sendLine(connection, HTTP_OK);
138602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            } else {
1391e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                // Proxying the request directly to the origin server.
1401e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                sendAugmentedRequestToHost(connection, server,
1411e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                        requestType, url, httpVersion);
142602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                            }
143602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        }
144602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    } catch (IOException ioe) {
1451e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1461e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                            Log.v(TAG, "Unable to connect to proxy " + proxy, ioe);
1471e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        }
148602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    }
149602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    if (server != null) {
150602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        break;
151602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    }
152602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
1531e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                if (list.isEmpty()) {
154602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    server = new Socket(host, port);
155602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    if (requestType.equals(CONNECT)) {
1561e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        skipToRequestBody(connection);
157602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        // No proxy to respond so we must.
158602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                        sendLine(connection, HTTP_OK);
159602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    } else {
1601e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        // Proxying the request directly to the origin server.
1611e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        sendAugmentedRequestToHost(connection, server,
1621e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                requestType, url, httpVersion);
163602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    }
164602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
165602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                // Pass data back and forth until complete.
1661e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                if (server != null) {
1671e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    SocketConnect.connect(connection, server);
1681e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                }
1691e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            } catch (Exception e) {
170602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                Log.d(TAG, "Problem Proxying", e);
171602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            }
172602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            try {
173602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                connection.close();
174602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            } catch (IOException ioe) {
1751e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                // Do nothing
1761e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            }
1771e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
1781e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
1791e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
1801e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Sends HTTP request-line (i.e. the first line in the request)
1811e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * that contains absolute path of a given absolute URI.
1821e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
1831e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param server server to send the request to.
1841e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param requestType type of the request, a.k.a. HTTP method.
1851e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param absoluteUri absolute URI which absolute path should be extracted.
1861e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param httpVersion version of HTTP, e.g. HTTP/1.1.
1871e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @throws IOException if the request-line cannot be sent.
1881e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
1891e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private void sendRequestLineWithPath(Socket server, String requestType,
1901e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                URI absoluteUri, String httpVersion) throws IOException {
1911e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
1921e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String absolutePath = getAbsolutePathFromAbsoluteURI(absoluteUri);
1931e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String outgoingRequestLine = String.format("%s %s %s",
1941e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    requestType, absolutePath, httpVersion);
1951e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            sendLine(server, outgoingRequestLine);
1961e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
197602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
1981e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
1991e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Extracts absolute path form a given URI. E.g., passing
2001e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * <code>http://google.com:80/execute?query=cat#top</code>
2011e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * will result in <code>/execute?query=cat#top</code>.
2021e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
2031e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param uri URI which absolute path has to be extracted,
2041e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @return the absolute path of the URI,
2051e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
2061e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private String getAbsolutePathFromAbsoluteURI(URI uri) {
2071e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String rawPath = uri.getRawPath();
2081e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String rawQuery = uri.getRawQuery();
2091e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String rawFragment = uri.getRawFragment();
2101e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            StringBuilder absolutePath = new StringBuilder();
2111e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2121e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            if (rawPath != null) {
2131e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                absolutePath.append(rawPath);
2141e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            } else {
2151e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                absolutePath.append("/");
2161e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            }
2171e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            if (rawQuery != null) {
2181e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                absolutePath.append("?").append(rawQuery);
2191e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            }
2201e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            if (rawFragment != null) {
2211e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                absolutePath.append("#").append(rawFragment);
222602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            }
2231e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            return absolutePath.toString();
224602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
225602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
226602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        private String getLine(InputStream inputStream) throws IOException {
2271e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            StringBuilder buffer = new StringBuilder();
228602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            int byteBuffer = inputStream.read();
229602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            if (byteBuffer < 0) return "";
230602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            do {
231602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                if (byteBuffer != '\r') {
232602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                    buffer.append((char)byteBuffer);
233602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
234602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                byteBuffer = inputStream.read();
235602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            } while ((byteBuffer != '\n') && (byteBuffer >= 0));
236602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
237602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            return buffer.toString();
238602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
239602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
240602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        private void sendLine(Socket socket, String line) throws IOException {
241602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            OutputStream os = socket.getOutputStream();
242602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            os.write(line.getBytes());
243602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            os.write('\r');
244602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            os.write('\n');
245602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            os.flush();
246602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
2471e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2481e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
2491e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Reads from socket until an empty line is read which indicates the end of HTTP headers.
2501e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
2511e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param socket socket to read from.
2521e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @throws IOException if an exception took place during the socket read.
2531e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
2541e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private void skipToRequestBody(Socket socket) throws IOException {
2551e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            while (getLine(socket.getInputStream()).length() != 0);
2561e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
2571e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2581e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
2591e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Sends an augmented request to the final host (DIRECT connection).
2601e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
2611e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param src socket to read HTTP headers from.The socket current position should point
2621e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *            to the beginning of the HTTP header section.
2631e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param dst socket to write the augmented request to.
2641e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param httpMethod original request http method.
2651e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param uri original request absolute URI.
2661e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param httpVersion original request http version.
2671e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @throws IOException if an exception took place during socket reads or writes.
2681e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
2691e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private void sendAugmentedRequestToHost(Socket src, Socket dst,
2701e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                String httpMethod, URI uri, String httpVersion) throws IOException {
2711e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2721e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            sendRequestLineWithPath(dst, httpMethod, uri, httpVersion);
2731e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            filterAndForwardRequestHeaders(src, dst);
2741e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2751e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            // Currently the proxy does not support keep-alive connections; therefore,
2761e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            // the proxy has to request the destination server to close the connection
2771e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            // after the destination server sent the response.
2781e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            sendLine(dst, "Connection: close");
2791e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2801e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            // Sends and empty line that indicates termination of the header section.
2811e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            sendLine(dst, "");
2821e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
2831e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
2841e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
2851e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Forwards original request headers filtering out the ones that have to be removed.
2861e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
2871e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param src source socket that contains original request headers.
2881e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param dst destination socket to send the filtered headers to.
2891e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @throws IOException if the data cannot be read from or written to the sockets.
2901e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
2911e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private void filterAndForwardRequestHeaders(Socket src, Socket dst) throws IOException {
2921e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            String line;
2931e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            do {
2941e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                line = getLine(src.getInputStream());
2951e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                if (line.length() > 0 && !shouldRemoveHeaderLine(line)) {
2961e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    sendLine(dst, line);
2971e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                }
2981e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            } while (line.length() > 0);
2991e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
3001e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov
3011e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        /**
3021e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * Returns true if a given header line has to be removed from the original request.
3031e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         *
3041e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @param line header line that should be analysed.
3051e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         * @return true if the header line should be removed and not forwarded to the destination.
3061e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov         */
3071e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        private boolean shouldRemoveHeaderLine(String line) {
3081e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            int colIndex = line.indexOf(":");
3091e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            if (colIndex != -1) {
3101e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                String headerName = line.substring(0, colIndex).trim();
3111e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                if (headerName.regionMatches(true, 0, HEADER_CONNECTION, 0,
3121e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                                      HEADER_CONNECTION.length())
3131e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        || headerName.regionMatches(true, 0, HEADER_PROXY_CONNECTION,
3141e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                                                          0, HEADER_PROXY_CONNECTION.length())) {
3151e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    return true;
3161e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                }
3171e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            }
3181e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            return false;
3191e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov        }
320602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    }
321602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
322602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    public ProxyServer() {
323602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        threadExecutor = Executors.newCachedThreadPool();
3246f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        mPort = -1;
3256f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        mCallback = null;
326602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    }
327602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
328602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    @Override
329602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    public void run() {
330602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        try {
3316f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            serverSocket = new ServerSocket(0);
332602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
3331e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            setPort(serverSocket.getLocalPort());
334602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
3351e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov            while (mIsRunning) {
3361e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                try {
3371e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    Socket socket = serverSocket.accept();
3381e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    // Only receive local connections.
3391e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    if (socket.getInetAddress().isLoopbackAddress()) {
3401e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        ProxyConnection parser = new ProxyConnection(socket);
341602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
3421e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        threadExecutor.execute(parser);
3431e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    } else {
3441e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                        socket.close();
3456f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk                    }
3461e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                } catch (IOException e) {
3471e64ab3e56f6efafc25301df5da21d69bb75b470Andrei Kapishnikov                    e.printStackTrace();
348602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                }
349602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            }
350602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        } catch (SocketException e) {
3516f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            Log.e(TAG, "Failed to start proxy server", e);
3526f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        } catch (IOException e1) {
3536f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            Log.e(TAG, "Failed to start proxy server", e1);
354602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
355602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
356602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        mIsRunning = false;
357602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    }
358602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
3596f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    public synchronized void setPort(int port) {
3606f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        if (mCallback != null) {
3616f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            try {
3626f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk                mCallback.setProxyPort(port);
3636f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            } catch (RemoteException e) {
3646f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk                Log.w(TAG, "Proxy failed to report port to PacManager", e);
3656f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            }
3666f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        }
3676f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        mPort = port;
3686f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    }
3696f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk
3706f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    public synchronized void setCallback(IProxyPortListener callback) {
3716f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        if (mPort != -1) {
3726f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            try {
3736f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk                callback.setProxyPort(mPort);
3746f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            } catch (RemoteException e) {
3756f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk                Log.w(TAG, "Proxy failed to report port to PacManager", e);
3766f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk            }
3776f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        }
3786f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        mCallback = callback;
3796f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    }
3806f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk
381602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    public synchronized void startServer() {
382602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        mIsRunning = true;
383602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        start();
384602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    }
385602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk
386602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    public synchronized void stopServer() {
387602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        mIsRunning = false;
388602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        if (serverSocket != null) {
389602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            try {
390602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                serverSocket.close();
391602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                serverSocket = null;
392602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            } catch (IOException e) {
393602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk                e.printStackTrace();
394602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk            }
395602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk        }
396602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk    }
3976f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk
3986f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    public boolean isBound() {
3996f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        return (mPort != -1);
4006f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    }
4016f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk
4026f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    public int getPort() {
4036f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk        return mPort;
4046f8a68f49a7e8cf86104e721a1e8be7568b5f730Jason Monk    }
405602b232a06ede86999aa362a12eb28cbc782dc1dJason Monk}
406