1/**
2 * Copyright (c) 2013, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.proxyhandler;
17
18import android.net.ProxyProperties;
19import android.os.RemoteException;
20import android.util.Log;
21
22import com.android.net.IProxyPortListener;
23import com.google.android.collect.Lists;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.net.InetAddress;
29import java.net.InetSocketAddress;
30import java.net.Proxy;
31import java.net.ProxySelector;
32import java.net.ServerSocket;
33import java.net.Socket;
34import java.net.SocketException;
35import java.net.URI;
36import java.net.URISyntaxException;
37import java.util.List;
38import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40
41/**
42 * @hide
43 */
44public class ProxyServer extends Thread {
45
46    private static final String CONNECT = "CONNECT";
47    private static final String HTTP_OK = "HTTP/1.1 200 OK\n";
48
49    private static final String TAG = "ProxyServer";
50
51    private ExecutorService threadExecutor;
52
53    public boolean mIsRunning = false;
54
55    private ServerSocket serverSocket;
56    private int mPort;
57    private IProxyPortListener mCallback;
58
59    private class ProxyConnection implements Runnable {
60        private Socket connection;
61
62        private ProxyConnection(Socket connection) {
63            this.connection = connection;
64        }
65
66        @Override
67        public void run() {
68            try {
69                String requestLine = getLine(connection.getInputStream());
70                if (requestLine == null) {
71                    connection.close();
72                    return;
73                }
74                String[] splitLine = requestLine.split(" ");
75                if (splitLine.length < 3) {
76                    connection.close();
77                    return;
78                }
79                String requestType = splitLine[0];
80                String urlString = splitLine[1];
81
82                String host = "";
83                int port = 80;
84
85                if (requestType.equals(CONNECT)) {
86                    String[] hostPortSplit = urlString.split(":");
87                    host = hostPortSplit[0];
88                    try {
89                        port = Integer.parseInt(hostPortSplit[1]);
90                    } catch (NumberFormatException nfe) {
91                        port = 443;
92                    }
93                    urlString = "Https://" + host + ":" + port;
94                } else {
95                    try {
96                        URI url = new URI(urlString);
97                        host = url.getHost();
98                        port = url.getPort();
99                        if (port < 0) {
100                            port = 80;
101                        }
102                    } catch (URISyntaxException e) {
103                        connection.close();
104                        return;
105                    }
106                }
107
108                List<Proxy> list = Lists.newArrayList();
109                try {
110                    list = ProxySelector.getDefault().select(new URI(urlString));
111                } catch (URISyntaxException e) {
112                    e.printStackTrace();
113                }
114                Socket server = null;
115                for (Proxy proxy : list) {
116                    try {
117                        if (!proxy.equals(Proxy.NO_PROXY)) {
118                            // Only Inets created by PacProxySelector.
119                            InetSocketAddress inetSocketAddress =
120                                    (InetSocketAddress)proxy.address();
121                            server = new Socket(inetSocketAddress.getHostName(),
122                                    inetSocketAddress.getPort());
123                            sendLine(server, requestLine);
124                        } else {
125                            server = new Socket(host, port);
126                            if (requestType.equals(CONNECT)) {
127                                while (getLine(connection.getInputStream()).length() != 0);
128                                // No proxy to respond so we must.
129                                sendLine(connection, HTTP_OK);
130                            } else {
131                                sendLine(server, requestLine);
132                            }
133                        }
134                    } catch (IOException ioe) {
135
136                    }
137                    if (server != null) {
138                        break;
139                    }
140                }
141                if (server == null) {
142                    server = new Socket(host, port);
143                    if (requestType.equals(CONNECT)) {
144                        while (getLine(connection.getInputStream()).length() != 0);
145                        // No proxy to respond so we must.
146                        sendLine(connection, HTTP_OK);
147                    } else {
148                        sendLine(server, requestLine);
149                    }
150                }
151                // Pass data back and forth until complete.
152                SocketConnect.connect(connection, server);
153            } catch (IOException e) {
154                Log.d(TAG, "Problem Proxying", e);
155            }
156            try {
157                connection.close();
158            } catch (IOException ioe) {
159
160            }
161        }
162
163        private String getLine(InputStream inputStream) throws IOException {
164            StringBuffer buffer = new StringBuffer();
165            int byteBuffer = inputStream.read();
166            if (byteBuffer < 0) return "";
167            do {
168                if (byteBuffer != '\r') {
169                    buffer.append((char)byteBuffer);
170                }
171                byteBuffer = inputStream.read();
172            } while ((byteBuffer != '\n') && (byteBuffer >= 0));
173
174            return buffer.toString();
175        }
176
177        private void sendLine(Socket socket, String line) throws IOException {
178            OutputStream os = socket.getOutputStream();
179            os.write(line.getBytes());
180            os.write('\r');
181            os.write('\n');
182            os.flush();
183        }
184    }
185
186    public ProxyServer() {
187        threadExecutor = Executors.newCachedThreadPool();
188        mPort = -1;
189        mCallback = null;
190    }
191
192    @Override
193    public void run() {
194        try {
195            serverSocket = new ServerSocket(0);
196
197            if (serverSocket != null) {
198                setPort(serverSocket.getLocalPort());
199
200                while (mIsRunning) {
201                    try {
202                        Socket socket = serverSocket.accept();
203                        // Only receive local connections.
204                        if (socket.getInetAddress().isLoopbackAddress()) {
205                            ProxyConnection parser = new ProxyConnection(socket);
206
207                            threadExecutor.execute(parser);
208                        } else {
209                            socket.close();
210                        }
211                    } catch (IOException e) {
212                        e.printStackTrace();
213                    }
214                }
215            }
216        } catch (SocketException e) {
217            Log.e(TAG, "Failed to start proxy server", e);
218        } catch (IOException e1) {
219            Log.e(TAG, "Failed to start proxy server", e1);
220        }
221
222        mIsRunning = false;
223    }
224
225    public synchronized void setPort(int port) {
226        if (mCallback != null) {
227            try {
228                mCallback.setProxyPort(port);
229            } catch (RemoteException e) {
230                Log.w(TAG, "Proxy failed to report port to PacManager", e);
231            }
232        }
233        mPort = port;
234    }
235
236    public synchronized void setCallback(IProxyPortListener callback) {
237        if (mPort != -1) {
238            try {
239                callback.setProxyPort(mPort);
240            } catch (RemoteException e) {
241                Log.w(TAG, "Proxy failed to report port to PacManager", e);
242            }
243        }
244        mCallback = callback;
245    }
246
247    public synchronized void startServer() {
248        mIsRunning = true;
249        start();
250    }
251
252    public synchronized void stopServer() {
253        mIsRunning = false;
254        if (serverSocket != null) {
255            try {
256                serverSocket.close();
257                serverSocket = null;
258            } catch (IOException e) {
259                e.printStackTrace();
260            }
261        }
262    }
263
264    public boolean isBound() {
265        return (mPort != -1);
266    }
267
268    public int getPort() {
269        return mPort;
270    }
271}
272