Proxy.java revision 6f71b42bc4531618e2b050bdf9ad05a72ac2a565
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 java.net.Proxy retval; 142 try { 143 if (sGlobalProxyChangedObserver == null) { 144 registerContentObserversReadLocked(ctx); 145 parseGlobalProxyInfoReadLocked(ctx); 146 } 147 if (sGlobalProxySpec != null) { 148 // Proxy defined - Apply exclusion rules 149 if (isURLInExclusionListReadLocked(url, sGlobalProxySpec.exclusionList)) { 150 // Return no proxy 151 retval = java.net.Proxy.NO_PROXY; 152 } else { 153 retval = 154 new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress); 155 } 156 } else { 157 // If network is WiFi, return no proxy. 158 // Otherwise, return the Mobile Operator proxy. 159 if (!isNetworkWifi(ctx)) { 160 retval = getDefaultProxy(url); 161 } else { 162 retval = java.net.Proxy.NO_PROXY; 163 } 164 } 165 } finally { 166 sProxyInfoLock.readLock().unlock(); 167 } 168 if ((retval != java.net.Proxy.NO_PROXY) && (isLocalHost(url))) { 169 retval = java.net.Proxy.NO_PROXY; 170 } 171 return retval; 172 } 173 174 // TODO: deprecate this function 175 /** 176 * Return the proxy host set by the user. 177 * @param ctx A Context used to get the settings for the proxy host. 178 * @return String containing the host name. If the user did not set a host 179 * name it returns the default host. A null value means that no 180 * host is to be used. 181 */ 182 public static final String getHost(Context ctx) { 183 sProxyInfoLock.readLock().lock(); 184 try { 185 if (sGlobalProxyChangedObserver == null) { 186 registerContentObserversReadLocked(ctx); 187 parseGlobalProxyInfoReadLocked(ctx); 188 } 189 if (sGlobalProxySpec != null) { 190 InetSocketAddress sa = sGlobalProxySpec.proxyAddress; 191 return sa.getHostName(); 192 } 193 return getDefaultHost(); 194 } finally { 195 sProxyInfoLock.readLock().unlock(); 196 } 197 } 198 199 // TODO: deprecate this function 200 /** 201 * Return the proxy port set by the user. 202 * @param ctx A Context used to get the settings for the proxy port. 203 * @return The port number to use or -1 if no proxy is to be used. 204 */ 205 public static final int getPort(Context ctx) { 206 sProxyInfoLock.readLock().lock(); 207 try { 208 if (sGlobalProxyChangedObserver == null) { 209 registerContentObserversReadLocked(ctx); 210 parseGlobalProxyInfoReadLocked(ctx); 211 } 212 if (sGlobalProxySpec != null) { 213 InetSocketAddress sa = sGlobalProxySpec.proxyAddress; 214 return sa.getPort(); 215 } 216 return getDefaultPort(); 217 } finally { 218 sProxyInfoLock.readLock().unlock(); 219 } 220 } 221 222 // TODO: deprecate this function 223 /** 224 * Return the default proxy host specified by the carrier. 225 * @return String containing the host name or null if there is no proxy for 226 * this carrier. 227 */ 228 public static final String getDefaultHost() { 229 String host = SystemProperties.get("net.gprs.http-proxy"); 230 if ((host != null) && (host.length() != 0)) { 231 Uri u = Uri.parse(host); 232 host = u.getHost(); 233 return host; 234 } else { 235 return null; 236 } 237 } 238 239 // TODO: deprecate this function 240 /** 241 * Return the default proxy port specified by the carrier. 242 * @return The port number to be used with the proxy host or -1 if there is 243 * no proxy for this carrier. 244 */ 245 public static final int getDefaultPort() { 246 String host = SystemProperties.get("net.gprs.http-proxy"); 247 if ((host != null) && (host.length() != 0)) { 248 Uri u = Uri.parse(host); 249 return u.getPort(); 250 } else { 251 return -1; 252 } 253 } 254 255 private static final java.net.Proxy getDefaultProxy(String url) { 256 // TODO: This will go away when information is collected from ConnectivityManager... 257 // There are broadcast of network proxies, so they are parse manually. 258 String host = SystemProperties.get("net.gprs.http-proxy"); 259 if ((host != null) && (host.length() != 0)) { 260 Uri u = Uri.parse(host); 261 return new java.net.Proxy(java.net.Proxy.Type.HTTP, 262 new InetSocketAddress(u.getHost(), u.getPort())); 263 } else { 264 return java.net.Proxy.NO_PROXY; 265 } 266 } 267 268 // TODO: remove this function / deprecate 269 /** 270 * Returns the preferred proxy to be used by clients. This is a wrapper 271 * around {@link android.net.Proxy#getHost()}. Currently no proxy will 272 * be returned for localhost or if the active network is Wi-Fi. 273 * 274 * @param context the context which will be passed to 275 * {@link android.net.Proxy#getHost()} 276 * @param url the target URL for the request 277 * @note Calling this method requires permission 278 * android.permission.ACCESS_NETWORK_STATE 279 * @return The preferred proxy to be used by clients, or null if there 280 * is no proxy. 281 * {@hide} 282 */ 283 public static final HttpHost getPreferredHttpHost(Context context, 284 String url) { 285 java.net.Proxy prefProxy = getProxy(context, url); 286 if (prefProxy.equals(java.net.Proxy.NO_PROXY)) { 287 return null; 288 } else { 289 InetSocketAddress sa = (InetSocketAddress)prefProxy.address(); 290 return new HttpHost(sa.getHostName(), sa.getPort(), "http"); 291 } 292 } 293 294 private static final boolean isLocalHost(String url) { 295 if (url == null) { 296 return false; 297 } 298 try { 299 final URI uri = URI.create(url); 300 final String host = uri.getHost(); 301 if (host != null) { 302 if (host.equalsIgnoreCase("localhost")) { 303 return true; 304 } 305 if (InetAddress.getByName(host).isLoopbackAddress()) { 306 return true; 307 } 308 } 309 } catch (UnknownHostException uex) { 310 // Ignore (INetworkSystem.ipStringToByteArray) 311 } catch (IllegalArgumentException iex) { 312 // Ignore (URI.create) 313 } 314 return false; 315 } 316 317 private static final boolean isNetworkWifi(Context context) { 318 if (context == null) { 319 return false; 320 } 321 final ConnectivityManager connectivity = (ConnectivityManager) 322 context.getSystemService(Context.CONNECTIVITY_SERVICE); 323 if (connectivity != null) { 324 final NetworkInfo info = connectivity.getActiveNetworkInfo(); 325 if (info != null && 326 info.getType() == ConnectivityManager.TYPE_WIFI) { 327 return true; 328 } 329 } 330 return false; 331 } 332 333 private static class SettingsObserver extends ContentObserver { 334 335 private Context mContext; 336 337 SettingsObserver(Context ctx) { 338 super(new Handler(ctx.getMainLooper())); 339 mContext = ctx; 340 } 341 342 @Override 343 public void onChange(boolean selfChange) { 344 sProxyInfoLock.readLock().lock(); 345 parseGlobalProxyInfoReadLocked(mContext); 346 sProxyInfoLock.readLock().unlock(); 347 } 348 } 349 350 private static final void registerContentObserversReadLocked(Context ctx) { 351 Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY); 352 Uri uriGlobalExclList = 353 Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); 354 355 // No lock upgrading (from read to write) allowed 356 sProxyInfoLock.readLock().unlock(); 357 sProxyInfoLock.writeLock().lock(); 358 try { 359 sGlobalProxyChangedObserver = new SettingsObserver(ctx); 360 } finally { 361 // Downgrading locks (from write to read) is allowed 362 sProxyInfoLock.readLock().lock(); 363 sProxyInfoLock.writeLock().unlock(); 364 } 365 ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false, 366 sGlobalProxyChangedObserver); 367 ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false, 368 sGlobalProxyChangedObserver); 369 } 370 371 private static final void parseGlobalProxyInfoReadLocked(Context ctx) { 372 ContentResolver contentResolver = ctx.getContentResolver(); 373 String proxyHost = Settings.Secure.getString( 374 contentResolver, 375 Settings.Secure.HTTP_PROXY); 376 if ((proxyHost == null) || (proxyHost.length() == 0)) { 377 // Clear signal 378 sProxyInfoLock.readLock().unlock(); 379 sProxyInfoLock.writeLock().lock(); 380 sGlobalProxySpec = null; 381 sProxyInfoLock.readLock().lock(); 382 sProxyInfoLock.writeLock().unlock(); 383 return; 384 } 385 String exclusionListSpec = Settings.Secure.getString( 386 contentResolver, 387 Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); 388 String host = parseHost(proxyHost); 389 int port = parsePort(proxyHost); 390 if (proxyHost != null) { 391 sGlobalProxySpec = new ProxySpec(); 392 sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port); 393 if ((exclusionListSpec != null) && (exclusionListSpec.length() != 0)) { 394 String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(","); 395 String[] processedEntries = new String[exclusionListEntries.length]; 396 for (int i = 0; i < exclusionListEntries.length; i++) { 397 String entry = exclusionListEntries[i].trim(); 398 if (entry.startsWith(".")) { 399 entry = entry.substring(1); 400 } 401 processedEntries[i] = entry; 402 } 403 sProxyInfoLock.readLock().unlock(); 404 sProxyInfoLock.writeLock().lock(); 405 sGlobalProxySpec.exclusionList = processedEntries; 406 } else { 407 sProxyInfoLock.readLock().unlock(); 408 sProxyInfoLock.writeLock().lock(); 409 sGlobalProxySpec.exclusionList = null; 410 } 411 } else { 412 sProxyInfoLock.readLock().unlock(); 413 sProxyInfoLock.writeLock().lock(); 414 sGlobalProxySpec = null; 415 } 416 sProxyInfoLock.readLock().lock(); 417 sProxyInfoLock.writeLock().unlock(); 418 } 419 420 /** 421 * Validate syntax of hostname, port and exclusion list entries 422 * {@hide} 423 */ 424 public static void validate(String hostname, String port, String exclList) { 425 Matcher match = HOSTNAME_PATTERN.matcher(hostname); 426 Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); 427 428 if (!match.matches()) { 429 throw new IllegalArgumentException(); 430 } 431 432 if (!listMatch.matches()) { 433 throw new IllegalArgumentException(); 434 } 435 436 if (hostname.length() > 0 && port.length() == 0) { 437 throw new IllegalArgumentException(); 438 } 439 440 if (port.length() > 0) { 441 if (hostname.length() == 0) { 442 throw new IllegalArgumentException(); 443 } 444 int portVal = -1; 445 try { 446 portVal = Integer.parseInt(port); 447 } catch (NumberFormatException ex) { 448 throw new IllegalArgumentException(); 449 } 450 if (portVal <= 0 || portVal > 0xFFFF) { 451 throw new IllegalArgumentException(); 452 } 453 } 454 } 455} 456