diff options
author | Jason Monk <jmonk@google.com> | 2013-07-03 17:04:33 -0400 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2013-08-07 21:01:39 -0400 |
commit | 602b232a06ede86999aa362a12eb28cbc782dc1d (patch) | |
tree | 07a2168ba91425f81bd7ac9c9311adb78114cc13 /packages/services/Proxy | |
parent | 55db1e1218971105e68ba9d451b2e0b1e9e5f9fb (diff) | |
download | frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.zip frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.tar.gz frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.tar.bz2 |
Add PAC File support for proxy configuration
PAC (Proxy auto-config) files contain a single javascript function,
FindProxyForURL(url, host). It gets called to determine what proxy should be
used for a specific request.
This adds PAC support to the system. The ProxyProperties has been modified
to hold the PAC file when one is present. The Proxy method
setHttpProxySystemProperty has been modified to insert a PacProxySelector
as the default ProxySelector when it is required. This new ProxySelector
makes calls to the ConnectivityService to parse the PAC file.
The ConnectivityService and the WifiConfigStore have been modified to support
saving the extra PAC file data.
The ConnectivityService now has a class attached (PacProxyNative) that
interfaces to the native calls for PAC files. The parsing of the PAC file
is handled by libpac (which is being added to external/) which utilizes
libv8 to parse the javascript.
As a fallback to applications that don't use the java ProxySelector, the proxy
is setup to point to a local proxy server that will handle the pac parsing.
bug:10182711
Change-Id: I5eb8df893c632fd3e1b732385cb7720ad646f401
Diffstat (limited to 'packages/services/Proxy')
8 files changed, 445 insertions, 0 deletions
diff --git a/packages/services/Proxy/Android.mk b/packages/services/Proxy/Android.mk new file mode 100644 index 0000000..d5546b2 --- /dev/null +++ b/packages/services/Proxy/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := ProxyHandler +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml new file mode 100644 index 0000000..02475c0 --- /dev/null +++ b/packages/services/Proxy/AndroidManifest.xml @@ -0,0 +1,25 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.proxyhandler" + coreApp="true"> + + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:persistent="true" + android:label="@string/app_label" + android:process="com.android.proxyhandler"> + + <service android:name=".ProxyService" + android:exported="true"> + </service> + + <receiver android:name=".ProxyServiceReceiver"> + + <intent-filter> + <action android:name="android.intent.action.PROXY_CHANGE" /> + </intent-filter> + </receiver> + + </application> +</manifest> diff --git a/packages/services/Proxy/com/android/net/IProxyService.aidl b/packages/services/Proxy/com/android/net/IProxyService.aidl new file mode 100644 index 0000000..7e9ed79 --- /dev/null +++ b/packages/services/Proxy/com/android/net/IProxyService.aidl @@ -0,0 +1,16 @@ +package com.android.net; + +/** @hide */ +interface IProxyService +{ + /** + * Keep up-to-date with + * frameworks/base/packages/services/PacProcessor/IProxyService.h + */ + String resolvePacFile(String host, String url); + + int setPacFile(String scriptContents); + + int startPacSystem(); + int stopPacSystem(); +} diff --git a/packages/services/Proxy/res/values/strings.xml b/packages/services/Proxy/res/values/strings.xml new file mode 100644 index 0000000..6188d79 --- /dev/null +++ b/packages/services/Proxy/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">ProxyHandler</string> + +</resources> diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java new file mode 100644 index 0000000..5d8689e --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -0,0 +1,219 @@ + +package com.android.proxyhandler; + +import android.net.ProxyProperties; +import android.util.Log; + +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @hide + */ +public class ProxyServer extends Thread { + + private static final String CONNECT = "CONNECT"; + private static final String HTTP_OK = "HTTP/1.1 200 OK\n"; + + private static final String TAG = "ProxyServer"; + + private ExecutorService threadExecutor; + + public boolean mIsRunning = false; + + private ServerSocket serverSocket; + private ProxyProperties mProxy; + + private class ProxyConnection implements Runnable { + private Socket connection; + + private ProxyConnection(Socket connection) { + this.connection = connection; + } + + @Override + public void run() { + try { + android.net.Proxy.setHttpProxySystemProperty(mProxy); + + String requestLine = getLine(connection.getInputStream()); + if (requestLine == null) { + connection.close(); + return; + } + String[] splitLine = requestLine.split(" "); + if (splitLine.length < 3) { + connection.close(); + return; + } + String requestType = splitLine[0]; + String urlString = splitLine[1]; + + String host = ""; + int port = 80; + + if (requestType.equals(CONNECT)) { + String[] hostPortSplit = urlString.split(":"); + host = hostPortSplit[0]; + try { + port = Integer.parseInt(hostPortSplit[1]); + } catch (NumberFormatException nfe) { + port = 443; + } + urlString = "Https://" + host + ":" + port; + } else { + try { + URI url = new URI(urlString); + host = url.getHost(); + port = url.getPort(); + if (port < 0) { + port = 80; + } + } catch (URISyntaxException e) { + connection.close(); + return; + } + } + + List<Proxy> list = Lists.newArrayList(); + try { + list = ProxySelector.getDefault().select(new URI(urlString)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + Socket server = null; + for (Proxy proxy : list) { + try { + if (!proxy.equals(Proxy.NO_PROXY)) { + // Only Inets created by PacProxySelector. + InetSocketAddress inetSocketAddress = + (InetSocketAddress)list.get(0).address(); + server = new Socket(inetSocketAddress.getAddress(), + inetSocketAddress.getPort()); + sendLine(server, requestLine); + } else { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + } catch (IOException ioe) { + + } + if (server != null) { + break; + } + } + if (server == null) { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + // Pass data back and forth until complete. + SocketConnect.connect(connection, server); + } catch (IOException e) { + Log.d(TAG, "Problem Proxying", e); + } + try { + connection.close(); + } catch (IOException ioe) { + + } + } + + private String getLine(InputStream inputStream) throws IOException { + StringBuffer buffer = new StringBuffer(); + int byteBuffer = inputStream.read(); + if (byteBuffer < 0) return ""; + do { + if (byteBuffer != '\r') { + buffer.append((char)byteBuffer); + } + byteBuffer = inputStream.read(); + } while ((byteBuffer != '\n') && (byteBuffer >= 0)); + + return buffer.toString(); + } + + private void sendLine(Socket socket, String line) throws IOException { + OutputStream os = socket.getOutputStream(); + os.write(line.getBytes()); + os.write('\r'); + os.write('\n'); + os.flush(); + } + } + + public ProxyServer() { + threadExecutor = Executors.newCachedThreadPool(); + } + + @Override + public void run() { + try { + serverSocket = new ServerSocket(ProxyService.PORT); + + serverSocket.setReuseAddress(true); + + while (mIsRunning) { + try { + ProxyConnection parser = new ProxyConnection(serverSocket.accept()); + + threadExecutor.execute(parser); + } catch (IOException e) { + e.printStackTrace(); + } + } + } catch (SocketException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + mIsRunning = false; + } + + public synchronized void startServer() { + mIsRunning = true; + start(); + } + + public synchronized void stopServer() { + mIsRunning = false; + if (serverSocket != null) { + try { + serverSocket.close(); + serverSocket = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void setProxy(ProxyProperties proxy) { + mProxy = proxy; + } +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java new file mode 100644 index 0000000..0aea5ee --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java @@ -0,0 +1,68 @@ +package com.android.proxyhandler; + +import android.app.Service; +import android.content.Intent; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; + +/** + * @hide + */ +public class ProxyService extends Service { + + private static ProxyServer server = null; + + /** Keep these values up-to-date with PacManager.java */ + public static final String KEY_PROXY = "keyProxy"; + public static final String HOST = "localhost"; + public static final int PORT = 8182; + public static final String EXCL_LIST = ""; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + handleCommand(intent); + } + return START_STICKY; + } + + private void handleCommand(Intent intent) { + Bundle bundle = intent.getExtras(); + ProxyProperties proxy = null; + if ((bundle != null) && bundle.containsKey(Proxy.EXTRA_PROXY_INFO)) { + proxy = bundle.getParcelable(Proxy.EXTRA_PROXY_INFO); + if ((proxy != null) && !TextUtils.isEmpty(proxy.getPacFileUrl())) { + startProxy(proxy); + } else { + stopSelf(); + } + } else { + stopSelf(); + } + } + + + private void startProxy(ProxyProperties proxy) { + if (server == null) { + server = new ProxyServer(); + server.startServer(); + } + server.setProxy(proxy); + } + + @Override + public void onDestroy() { + if (server != null) { + server.stopServer(); + server = null; + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +}
\ No newline at end of file diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java new file mode 100644 index 0000000..f5c2ca5 --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java @@ -0,0 +1,22 @@ +package com.android.proxyhandler; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Proxy; +import android.os.Bundle; + +public class ProxyServiceReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Intent service = new Intent(context, ProxyService.class); + Bundle bundle = intent.getExtras(); + if (bundle != null) { + service.putExtra(Proxy.EXTRA_PROXY_INFO, + bundle.getParcelable(Proxy.EXTRA_PROXY_INFO)); + } + context.startService(service); + } + +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java new file mode 100644 index 0000000..0d7df7f --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java @@ -0,0 +1,59 @@ +package com.android.proxyhandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * @hide + */ +public class SocketConnect extends Thread { + + private InputStream from; + private OutputStream to; + + public SocketConnect(Socket from, Socket to) throws IOException { + this.from = from.getInputStream(); + this.to = to.getOutputStream(); + start(); + } + + @Override + public void run() { + final byte[] buffer = new byte[512]; + + try { + while (true) { + int r = from.read(buffer); + if (r < 0) { + break; + } + to.write(buffer, 0, r); + } + from.close(); + to.close(); + } catch (IOException io) { + + } + } + + public static void connect(Socket first, Socket second) { + try { + SocketConnect sc1 = new SocketConnect(first, second); + SocketConnect sc2 = new SocketConnect(second, first); + try { + sc1.join(); + } catch (InterruptedException e) { + } + try { + sc2.join(); + } catch (InterruptedException e) { + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + +} |