Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16fb7910 authored by Oscar Montemayor's avatar Oscar Montemayor
Browse files

Global Proxy changes to proxy class.

Change-Id: Ib2da33670b1da33c0b35b60f4fcbd0bc084e616a
parent 37b4a3c2
Loading
Loading
Loading
Loading
+310 −56
Original line number Diff line number Diff line
@@ -16,32 +16,168 @@

package android.net;

import org.apache.http.HttpHost;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import junit.framework.Assert;

import org.apache.http.HttpHost;

/**
 * A convenience class for accessing the user and default proxy
 * settings.
 */
final public class Proxy {
public final class Proxy {

    // Set to true to enable extra debugging.
    static final private boolean DEBUG = false;
    private static final boolean DEBUG = false;

    static final public String PROXY_CHANGE_ACTION =
    public static final String PROXY_CHANGE_ACTION =
        "android.intent.action.PROXY_CHANGE";

    private static ReadWriteLock sProxyInfoLock = new ReentrantReadWriteLock();

    private static SettingsObserver sGlobalProxyChangedObserver = null;

    private static ProxySpec sGlobalProxySpec = null;

    // Hostname / IP REGEX validation
    // Matches blank input, ips, and domain names
    private static final String NAME_IP_REGEX =
        "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";

    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";

    private static final Pattern HOSTNAME_PATTERN;

    private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX
        + ")+(,(.?" + NAME_IP_REGEX + "))*$";

    private static final Pattern EXCLLIST_PATTERN;

    static {
        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
    }

    private static class ProxySpec {
        String[] exclusionList = null;
        InetSocketAddress proxyAddress = null;
        public ProxySpec() { };
    }

    private static boolean isURLInExclusionListReadLocked(String url, String[] exclusionList) {
        if (exclusionList == null) {
            return false;
        }
        Uri u = Uri.parse(url);
        String urlDomain = u.getHost();
        // If the domain is defined as ".android.com" or "android.com", we wish to match
        // http://android.com as well as http://xxx.android.com , but not
        // http://myandroid.com . This code works out the logic.
        for (String excludedDomain : exclusionList) {
            String dotDomain = "." + excludedDomain;
            if (urlDomain.equals(excludedDomain)) {
                return true;
            }
            if (urlDomain.endsWith(dotDomain)) {
                return true;
            }
        }
        // No match
        return false;
    }

    private static String parseHost(String proxySpec) {
        int i = proxySpec.indexOf(':');
        if (i == -1) {
            if (DEBUG) {
                Assert.assertTrue(proxySpec.length() == 0);
            }
            return null;
        }
        return proxySpec.substring(0, i);
    }

    private static int parsePort(String proxySpec) {
        int i = proxySpec.indexOf(':');
        if (i == -1) {
            if (DEBUG) {
                Assert.assertTrue(proxySpec.length() == 0);
            }
            return -1;
        }
        if (DEBUG) {
            Assert.assertTrue(i < proxySpec.length());
        }
        return Integer.parseInt(proxySpec.substring(i+1));
    }

    /**
     * Return the proxy object to be used for the URL given as parameter.
     * @param ctx A Context used to get the settings for the proxy host.
     * @param url A URL to be accessed. Used to evaluate exclusion list.
     * @return Proxy (java.net) object containing the host name. If the
     *         user did not set a hostname it returns the default host.
     *         A null value means that no host is to be used.
     * {@hide}
     */
    public static final java.net.Proxy getProxy(Context ctx, String url) {
        sProxyInfoLock.readLock().lock();
        try {
            if (sGlobalProxyChangedObserver == null) {
                registerContentObserversReadLocked(ctx);
                parseGlobalProxyInfoReadLocked(ctx);
            }
            if (sGlobalProxySpec != null) {
                // Proxy defined - Apply exclusion rules
                if (isURLInExclusionListReadLocked(url, sGlobalProxySpec.exclusionList)) {
                    // Return no proxy
                    return java.net.Proxy.NO_PROXY;
                }
                java.net.Proxy retProxy =
                    new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress);
                sProxyInfoLock.readLock().unlock();
                if (isLocalHost(url)) {
                    return java.net.Proxy.NO_PROXY;
                }
                sProxyInfoLock.readLock().lock();
                return retProxy;
            } else {
                // If network is WiFi, return no proxy.
                // Otherwise, return the Mobile Operator proxy.
                if (!isNetworkWifi(ctx)) {
                    java.net.Proxy retProxy = getDefaultProxy(url);
                    sProxyInfoLock.readLock().unlock();
                    if (isLocalHost(url)) {
                        return java.net.Proxy.NO_PROXY;
                    }
                    sProxyInfoLock.readLock().lock();
                    return retProxy;
                } else {
                    return java.net.Proxy.NO_PROXY;
                }
            }
        } finally {
            sProxyInfoLock.readLock().unlock();
        }
    }

    // TODO: deprecate this function
    /**
     * Return the proxy host set by the user.
     * @param ctx A Context used to get the settings for the proxy host.
@@ -49,58 +185,53 @@ final public class Proxy {
     *         name it returns the default host. A null value means that no
     *         host is to be used.
     */
    static final public String getHost(Context ctx) {
        ContentResolver contentResolver = ctx.getContentResolver();
        Assert.assertNotNull(contentResolver);
        String host = Settings.Secure.getString(
                contentResolver,
                Settings.Secure.HTTP_PROXY);
        if (host != null) {
            int i = host.indexOf(':');
            if (i == -1) {
                if (DEBUG) {
                    Assert.assertTrue(host.length() == 0);
                }
                return null;
    public static final String getHost(Context ctx) {
        sProxyInfoLock.readLock().lock();
        try {
            if (sGlobalProxyChangedObserver == null) {
                registerContentObserversReadLocked(ctx);
                parseGlobalProxyInfoReadLocked(ctx);
            }
            return host.substring(0, i);
            if (sGlobalProxySpec != null) {
                InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
                return sa.getHostName();
            }
            return getDefaultHost();
        } finally {
            sProxyInfoLock.readLock().unlock();
        }
    }

    // TODO: deprecate this function
    /**
     * Return the proxy port set by the user.
     * @param ctx A Context used to get the settings for the proxy port.
     * @return The port number to use or -1 if no proxy is to be used.
     */
    static final public int getPort(Context ctx) {
        ContentResolver contentResolver = ctx.getContentResolver();
        Assert.assertNotNull(contentResolver);
        String host = Settings.Secure.getString(
                contentResolver,
                Settings.Secure.HTTP_PROXY);
        if (host != null) {
            int i = host.indexOf(':');
            if (i == -1) {
                if (DEBUG) {
                    Assert.assertTrue(host.length() == 0);
                }
                return -1;
            }
            if (DEBUG) {
                Assert.assertTrue(i < host.length());
    public static final int getPort(Context ctx) {
        sProxyInfoLock.readLock().lock();
        try {
            if (sGlobalProxyChangedObserver == null) {
                registerContentObserversReadLocked(ctx);
                parseGlobalProxyInfoReadLocked(ctx);
            }
            return Integer.parseInt(host.substring(i+1));
            if (sGlobalProxySpec != null) {
                InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
                return sa.getPort();
            }
            return getDefaultPort();
        } finally {
            sProxyInfoLock.readLock().unlock();
        }
    }

    // TODO: deprecate this function
    /**
     * Return the default proxy host specified by the carrier.
     * @return String containing the host name or null if there is no proxy for
     * this carrier.
     */
    static final public String getDefaultHost() {
    public static final String getDefaultHost() {
        String host = SystemProperties.get("net.gprs.http-proxy");
        if (host != null) {
            Uri u = Uri.parse(host);
@@ -111,12 +242,13 @@ final public class Proxy {
        }
    }

    // TODO: deprecate this function
    /**
     * Return the default proxy port specified by the carrier.
     * @return The port number to be used with the proxy host or -1 if there is
     * no proxy for this carrier.
     */
    static final public int getDefaultPort() {
    public static final int getDefaultPort() {
        String host = SystemProperties.get("net.gprs.http-proxy");
        if (host != null) {
            Uri u = Uri.parse(host);
@@ -126,6 +258,20 @@ final public class Proxy {
        }
    }

    private static final java.net.Proxy getDefaultProxy(String url) {
        // TODO: This will go away when information is collected from ConnectivityManager...
        // There are broadcast of network proxies, so they are parse manually.
        String host = SystemProperties.get("net.gprs.http-proxy");
        if (host != null) {
            Uri u = Uri.parse(host);
            return new java.net.Proxy(java.net.Proxy.Type.HTTP,
                    new InetSocketAddress(u.getHost(), u.getPort()));
        } else {
            return java.net.Proxy.NO_PROXY;
        }
    }

    // TODO: remove this function / deprecate
    /**
     * Returns the preferred proxy to be used by clients. This is a wrapper
     * around {@link android.net.Proxy#getHost()}. Currently no proxy will
@@ -138,26 +284,23 @@ final public class Proxy {
     * android.permission.ACCESS_NETWORK_STATE
     * @return The preferred proxy to be used by clients, or null if there
     * is no proxy.
     *
     * {@hide}
     */
    static final public HttpHost getPreferredHttpHost(Context context,
    public static final HttpHost getPreferredHttpHost(Context context,
            String url) {
        if (!isLocalHost(url) && !isNetworkWifi(context)) {
            final String proxyHost = Proxy.getHost(context);
            if (proxyHost != null) {
                return new HttpHost(proxyHost, Proxy.getPort(context), "http");
            }
        }

        java.net.Proxy prefProxy = getProxy(context, url);
        if (prefProxy.equals(java.net.Proxy.NO_PROXY)) {
            return null;
        } else {
            InetSocketAddress sa = (InetSocketAddress)prefProxy.address();
            return new HttpHost(sa.getHostName(), sa.getPort(), "http");
        }
    }

    static final private boolean isLocalHost(String url) {
    private static final boolean isLocalHost(String url) {
        if (url == null) {
            return false;
        }

        try {
            final URI uri = URI.create(url);
            final String host = uri.getHost();
@@ -174,15 +317,13 @@ final public class Proxy {
        } catch (IllegalArgumentException iex) {
            // Ignore (URI.create)
        }

        return false;
    }

    static final private boolean isNetworkWifi(Context context) {
    private static final boolean isNetworkWifi(Context context) {
        if (context == null) {
            return false;
        }

        final ConnectivityManager connectivity = (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity != null) {
@@ -192,7 +333,120 @@ final public class Proxy {
                return true;
            }
        }

        return false;
    }
};

    private static class SettingsObserver extends ContentObserver {

        private Context mContext;

        SettingsObserver(Context ctx) {
            super(new Handler());
            mContext = ctx;
        }

        @Override
        public void onChange(boolean selfChange) {
            sProxyInfoLock.readLock().lock();
            parseGlobalProxyInfoReadLocked(mContext);
            sProxyInfoLock.readLock().unlock();
        }
    }

    private static final void registerContentObserversReadLocked(Context ctx) {
        Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY);
        Uri uriGlobalExclList =
            Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);

        // No lock upgrading (from read to write) allowed
        sProxyInfoLock.readLock().unlock();
        sProxyInfoLock.writeLock().lock();
        sGlobalProxyChangedObserver = new SettingsObserver(ctx);
        // Downgrading locks (from write to read) is allowed
        sProxyInfoLock.readLock().lock();
        sProxyInfoLock.writeLock().unlock();
        ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false,
                sGlobalProxyChangedObserver);
        ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false,
                sGlobalProxyChangedObserver);
    }

    private static final void parseGlobalProxyInfoReadLocked(Context ctx) {
        ContentResolver contentResolver = ctx.getContentResolver();
        String proxyHost =  Settings.Secure.getString(
                contentResolver,
                Settings.Secure.HTTP_PROXY);
        if (proxyHost == null) {
            return;
        }
        String exclusionListSpec = Settings.Secure.getString(
                contentResolver,
                Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);
        String host = parseHost(proxyHost);
        int port = parsePort(proxyHost);
        if (proxyHost != null) {
            sGlobalProxySpec = new ProxySpec();
            sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port);
            if (exclusionListSpec != null) {
                String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(",");
                String[] processedEntries = new String[exclusionListEntries.length];
                for (int i = 0; i < exclusionListEntries.length; i++) {
                    String entry = exclusionListEntries[i].trim();
                    if (entry.startsWith(".")) {
                        entry = entry.substring(1);
                    }
                    processedEntries[i] = entry;
                }
                sProxyInfoLock.readLock().unlock();
                sProxyInfoLock.writeLock().lock();
                sGlobalProxySpec.exclusionList = processedEntries;
            } else {
                sProxyInfoLock.readLock().unlock();
                sProxyInfoLock.writeLock().lock();
                sGlobalProxySpec.exclusionList = null;
            }
        } else {
            sProxyInfoLock.readLock().unlock();
            sProxyInfoLock.writeLock().lock();
            sGlobalProxySpec = null;
        }
        sProxyInfoLock.readLock().lock();
        sProxyInfoLock.writeLock().unlock();
    }

    /**
     * Validate syntax of hostname, port and exclusion list entries
     * {@hide}
     */
    public static void validate(String hostname, String port, String exclList) {
        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);

        if (!match.matches()) {
            throw new IllegalArgumentException();
        }

        if (!listMatch.matches()) {
            throw new IllegalArgumentException();
        }

        if (hostname.length() > 0 && port.length() == 0) {
            throw new IllegalArgumentException();
        }

        if (port.length() > 0) {
            if (hostname.length() == 0) {
                throw new IllegalArgumentException();
            }
            int portVal = -1;
            try {
                portVal = Integer.parseInt(port);
            } catch (NumberFormatException ex) {
                throw new IllegalArgumentException();
            }
            if (portVal <= 0 || portVal > 0xFFFF) {
                throw new IllegalArgumentException();
            }
        }
    }
}
+16 −1
Original line number Diff line number Diff line
@@ -2321,10 +2321,25 @@ public final class Settings {
        public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";

        /**
         * Host name and port for a user-selected proxy.
         * Host name and port for global proxy.
         */
        public static final String HTTP_PROXY = "http_proxy";

        /**
         * Exclusion list for global proxy. This string contains a list of comma-separated
         * domains where the global proxy does not apply. Domains should be listed in a comma-
         * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com"
         * @hide
         */
        public static final String HTTP_PROXY_EXCLUSION_LIST = "http_proxy_exclusion_list";

        /**
         * Enables the UI setting to allow the user to specify the global HTTP proxy
         * and associated exclusion list.
         * @hide
         */
        public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy";

        /**
         * Whether the package installer should allow installation of apps downloaded from
         * sources other than the Android Market (vending machine).