Proxy.java revision 16fb7910fd3a86780edf4e65b529542ae95c26cd
1/* 2 * Copyright (C) 2007 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 */ 16 17package android.net; 18 19import android.content.ContentResolver; 20import android.content.Context; 21import android.database.ContentObserver; 22import android.os.Handler; 23import android.os.SystemProperties; 24import android.provider.Settings; 25 26import java.net.InetAddress; 27import java.net.InetSocketAddress; 28import java.net.SocketAddress; 29import java.net.URI; 30import java.net.UnknownHostException; 31import java.util.concurrent.locks.ReadWriteLock; 32import java.util.concurrent.locks.ReentrantReadWriteLock; 33import java.util.regex.Matcher; 34import java.util.regex.Pattern; 35 36import junit.framework.Assert; 37 38import org.apache.http.HttpHost; 39 40/** 41 * A convenience class for accessing the user and default proxy 42 * settings. 43 */ 44public final class Proxy { 45 46 // Set to true to enable extra debugging. 47 private static final boolean DEBUG = false; 48 49 public static final String PROXY_CHANGE_ACTION = 50 "android.intent.action.PROXY_CHANGE"; 51 52 private static ReadWriteLock sProxyInfoLock = new ReentrantReadWriteLock(); 53 54 private static SettingsObserver sGlobalProxyChangedObserver = null; 55 56 private static ProxySpec sGlobalProxySpec = null; 57 58 // Hostname / IP REGEX validation 59 // Matches blank input, ips, and domain names 60 private static final String NAME_IP_REGEX = 61 "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*"; 62 63 private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$"; 64 65 private static final Pattern HOSTNAME_PATTERN; 66 67 private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX 68 + ")+(,(.?" + NAME_IP_REGEX + "))*$"; 69 70 private static final Pattern EXCLLIST_PATTERN; 71 72 static { 73 HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); 74 EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); 75 } 76 77 private static class ProxySpec { 78 String[] exclusionList = null; 79 InetSocketAddress proxyAddress = null; 80 public ProxySpec() { }; 81 } 82 83 private static boolean isURLInExclusionListReadLocked(String url, String[] exclusionList) { 84 if (exclusionList == null) { 85 return false; 86 } 87 Uri u = Uri.parse(url); 88 String urlDomain = u.getHost(); 89 // If the domain is defined as ".android.com" or "android.com", we wish to match 90 // http://android.com as well as http://xxx.android.com , but not 91 // http://myandroid.com . This code works out the logic. 92 for (String excludedDomain : exclusionList) { 93 String dotDomain = "." + excludedDomain; 94 if (urlDomain.equals(excludedDomain)) { 95 return true; 96 } 97 if (urlDomain.endsWith(dotDomain)) { 98 return true; 99 } 100 } 101 // No match 102 return false; 103 } 104 105 private static String parseHost(String proxySpec) { 106 int i = proxySpec.indexOf(':'); 107 if (i == -1) { 108 if (DEBUG) { 109 Assert.assertTrue(proxySpec.length() == 0); 110 } 111 return null; 112 } 113 return proxySpec.substring(0, i); 114 } 115 116 private static int parsePort(String proxySpec) { 117 int i = proxySpec.indexOf(':'); 118 if (i == -1) { 119 if (DEBUG) { 120 Assert.assertTrue(proxySpec.length() == 0); 121 } 122 return -1; 123 } 124 if (DEBUG) { 125 Assert.assertTrue(i < proxySpec.length()); 126 } 127 return Integer.parseInt(proxySpec.substring(i+1)); 128 } 129 130 /** 131 * Return the proxy object to be used for the URL given as parameter. 132 * @param ctx A Context used to get the settings for the proxy host. 133 * @param url A URL to be accessed. Used to evaluate exclusion list. 134 * @return Proxy (java.net) object containing the host name. If the 135 * user did not set a hostname it returns the default host. 136 * A null value means that no host is to be used. 137 * {@hide} 138 */ 139 public static final java.net.Proxy getProxy(Context ctx, String url) { 140 sProxyInfoLock.readLock().lock(); 141 try { 142 if (sGlobalProxyChangedObserver == null) { 143 registerContentObserversReadLocked(ctx); 144 parseGlobalProxyInfoReadLocked(ctx); 145 } 146 if (sGlobalProxySpec != null) { 147 // Proxy defined - Apply exclusion rules 148 if (isURLInExclusionListReadLocked(url, sGlobalProxySpec.exclusionList)) { 149 // Return no proxy 150 return java.net.Proxy.NO_PROXY; 151 } 152 java.net.Proxy retProxy = 153 new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress); 154 sProxyInfoLock.readLock().unlock(); 155 if (isLocalHost(url)) { 156 return java.net.Proxy.NO_PROXY; 157 } 158 sProxyInfoLock.readLock().lock(); 159 return retProxy; 160 } else { 161 // If network is WiFi, return no proxy. 162 // Otherwise, return the Mobile Operator proxy. 163 if (!isNetworkWifi(ctx)) { 164 java.net.Proxy retProxy = getDefaultProxy(url); 165 sProxyInfoLock.readLock().unlock(); 166 if (isLocalHost(url)) { 167 return java.net.Proxy.NO_PROXY; 168 } 169 sProxyInfoLock.readLock().lock(); 170 return retProxy; 171 } else { 172 return java.net.Proxy.NO_PROXY; 173 } 174 } 175 } finally { 176 sProxyInfoLock.readLock().unlock(); 177 } 178 } 179 180 // TODO: deprecate this function 181 /** 182 * Return the proxy host set by the user. 183 * @param ctx A Context used to get the settings for the proxy host. 184 * @return String containing the host name. If the user did not set a host 185 * name it returns the default host. A null value means that no 186 * host is to be used. 187 */ 188 public static final String getHost(Context ctx) { 189 sProxyInfoLock.readLock().lock(); 190 try { 191 if (sGlobalProxyChangedObserver == null) { 192 registerContentObserversReadLocked(ctx); 193 parseGlobalProxyInfoReadLocked(ctx); 194 } 195 if (sGlobalProxySpec != null) { 196 InetSocketAddress sa = sGlobalProxySpec.proxyAddress; 197 return sa.getHostName(); 198 } 199 return getDefaultHost(); 200 } finally { 201 sProxyInfoLock.readLock().unlock(); 202 } 203 } 204 205 // TODO: deprecate this function 206 /** 207 * Return the proxy port set by the user. 208 * @param ctx A Context used to get the settings for the proxy port. 209 * @return The port number to use or -1 if no proxy is to be used. 210 */ 211 public static final int getPort(Context ctx) { 212 sProxyInfoLock.readLock().lock(); 213 try { 214 if (sGlobalProxyChangedObserver == null) { 215 registerContentObserversReadLocked(ctx); 216 parseGlobalProxyInfoReadLocked(ctx); 217 } 218 if (sGlobalProxySpec != null) { 219 InetSocketAddress sa = sGlobalProxySpec.proxyAddress; 220 return sa.getPort(); 221 } 222 return getDefaultPort(); 223 } finally { 224 sProxyInfoLock.readLock().unlock(); 225 } 226 } 227 228 // TODO: deprecate this function 229 /** 230 * Return the default proxy host specified by the carrier. 231 * @return String containing the host name or null if there is no proxy for 232 * this carrier. 233 */ 234 public static final String getDefaultHost() { 235 String host = SystemProperties.get("net.gprs.http-proxy"); 236 if (host != null) { 237 Uri u = Uri.parse(host); 238 host = u.getHost(); 239 return host; 240 } else { 241 return null; 242 } 243 } 244 245 // TODO: deprecate this function 246 /** 247 * Return the default proxy port specified by the carrier. 248 * @return The port number to be used with the proxy host or -1 if there is 249 * no proxy for this carrier. 250 */ 251 public static final int getDefaultPort() { 252 String host = SystemProperties.get("net.gprs.http-proxy"); 253 if (host != null) { 254 Uri u = Uri.parse(host); 255 return u.getPort(); 256 } else { 257 return -1; 258 } 259 } 260 261 private static final java.net.Proxy getDefaultProxy(String url) { 262 // TODO: This will go away when information is collected from ConnectivityManager... 263 // There are broadcast of network proxies, so they are parse manually. 264 String host = SystemProperties.get("net.gprs.http-proxy"); 265 if (host != null) { 266 Uri u = Uri.parse(host); 267 return new java.net.Proxy(java.net.Proxy.Type.HTTP, 268 new InetSocketAddress(u.getHost(), u.getPort())); 269 } else { 270 return java.net.Proxy.NO_PROXY; 271 } 272 } 273 274 // TODO: remove this function / deprecate 275 /** 276 * Returns the preferred proxy to be used by clients. This is a wrapper 277 * around {@link android.net.Proxy#getHost()}. Currently no proxy will 278 * be returned for localhost or if the active network is Wi-Fi. 279 * 280 * @param context the context which will be passed to 281 * {@link android.net.Proxy#getHost()} 282 * @param url the target URL for the request 283 * @note Calling this method requires permission 284 * android.permission.ACCESS_NETWORK_STATE 285 * @return The preferred proxy to be used by clients, or null if there 286 * is no proxy. 287 * {@hide} 288 */ 289 public static final HttpHost getPreferredHttpHost(Context context, 290 String url) { 291 java.net.Proxy prefProxy = getProxy(context, url); 292 if (prefProxy.equals(java.net.Proxy.NO_PROXY)) { 293 return null; 294 } else { 295 InetSocketAddress sa = (InetSocketAddress)prefProxy.address(); 296 return new HttpHost(sa.getHostName(), sa.getPort(), "http"); 297 } 298 } 299 300 private static final boolean isLocalHost(String url) { 301 if (url == null) { 302 return false; 303 } 304 try { 305 final URI uri = URI.create(url); 306 final String host = uri.getHost(); 307 if (host != null) { 308 if (host.equalsIgnoreCase("localhost")) { 309 return true; 310 } 311 if (InetAddress.getByName(host).isLoopbackAddress()) { 312 return true; 313 } 314 } 315 } catch (UnknownHostException uex) { 316 // Ignore (INetworkSystem.ipStringToByteArray) 317 } catch (IllegalArgumentException iex) { 318 // Ignore (URI.create) 319 } 320 return false; 321 } 322 323 private static final boolean isNetworkWifi(Context context) { 324 if (context == null) { 325 return false; 326 } 327 final ConnectivityManager connectivity = (ConnectivityManager) 328 context.getSystemService(Context.CONNECTIVITY_SERVICE); 329 if (connectivity != null) { 330 final NetworkInfo info = connectivity.getActiveNetworkInfo(); 331 if (info != null && 332 info.getType() == ConnectivityManager.TYPE_WIFI) { 333 return true; 334 } 335 } 336 return false; 337 } 338 339 private static class SettingsObserver extends ContentObserver { 340 341 private Context mContext; 342 343 SettingsObserver(Context ctx) { 344 super(new Handler()); 345 mContext = ctx; 346 } 347 348 @Override 349 public void onChange(boolean selfChange) { 350 sProxyInfoLock.readLock().lock(); 351 parseGlobalProxyInfoReadLocked(mContext); 352 sProxyInfoLock.readLock().unlock(); 353 } 354 } 355 356 private static final void registerContentObserversReadLocked(Context ctx) { 357 Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY); 358 Uri uriGlobalExclList = 359 Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); 360 361 // No lock upgrading (from read to write) allowed 362 sProxyInfoLock.readLock().unlock(); 363 sProxyInfoLock.writeLock().lock(); 364 sGlobalProxyChangedObserver = new SettingsObserver(ctx); 365 // Downgrading locks (from write to read) is allowed 366 sProxyInfoLock.readLock().lock(); 367 sProxyInfoLock.writeLock().unlock(); 368 ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false, 369 sGlobalProxyChangedObserver); 370 ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false, 371 sGlobalProxyChangedObserver); 372 } 373 374 private static final void parseGlobalProxyInfoReadLocked(Context ctx) { 375 ContentResolver contentResolver = ctx.getContentResolver(); 376 String proxyHost = Settings.Secure.getString( 377 contentResolver, 378 Settings.Secure.HTTP_PROXY); 379 if (proxyHost == null) { 380 return; 381 } 382 String exclusionListSpec = Settings.Secure.getString( 383 contentResolver, 384 Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); 385 String host = parseHost(proxyHost); 386 int port = parsePort(proxyHost); 387 if (proxyHost != null) { 388 sGlobalProxySpec = new ProxySpec(); 389 sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port); 390 if (exclusionListSpec != null) { 391 String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(","); 392 String[] processedEntries = new String[exclusionListEntries.length]; 393 for (int i = 0; i < exclusionListEntries.length; i++) { 394 String entry = exclusionListEntries[i].trim(); 395 if (entry.startsWith(".")) { 396 entry = entry.substring(1); 397 } 398 processedEntries[i] = entry; 399 } 400 sProxyInfoLock.readLock().unlock(); 401 sProxyInfoLock.writeLock().lock(); 402 sGlobalProxySpec.exclusionList = processedEntries; 403 } else { 404 sProxyInfoLock.readLock().unlock(); 405 sProxyInfoLock.writeLock().lock(); 406 sGlobalProxySpec.exclusionList = null; 407 } 408 } else { 409 sProxyInfoLock.readLock().unlock(); 410 sProxyInfoLock.writeLock().lock(); 411 sGlobalProxySpec = null; 412 } 413 sProxyInfoLock.readLock().lock(); 414 sProxyInfoLock.writeLock().unlock(); 415 } 416 417 /** 418 * Validate syntax of hostname, port and exclusion list entries 419 * {@hide} 420 */ 421 public static void validate(String hostname, String port, String exclList) { 422 Matcher match = HOSTNAME_PATTERN.matcher(hostname); 423 Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); 424 425 if (!match.matches()) { 426 throw new IllegalArgumentException(); 427 } 428 429 if (!listMatch.matches()) { 430 throw new IllegalArgumentException(); 431 } 432 433 if (hostname.length() > 0 && port.length() == 0) { 434 throw new IllegalArgumentException(); 435 } 436 437 if (port.length() > 0) { 438 if (hostname.length() == 0) { 439 throw new IllegalArgumentException(); 440 } 441 int portVal = -1; 442 try { 443 portVal = Integer.parseInt(port); 444 } catch (NumberFormatException ex) { 445 throw new IllegalArgumentException(); 446 } 447 if (portVal <= 0 || portVal > 0xFFFF) { 448 throw new IllegalArgumentException(); 449 } 450 } 451 } 452} 453