1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package sun.net.spi; 28 29 import java.net.InetSocketAddress; 30 import java.net.Proxy; 31 import java.net.ProxySelector; 32 import java.net.SocketAddress; 33 import java.net.URI; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.io.IOException; 37 import java.security.AccessController; 38 import java.security.PrivilegedAction; 39 import java.util.StringJoiner; 40 import java.util.regex.Pattern; 41 import sun.net.NetProperties; 42 import sun.net.SocksProxy; 43 import static java.util.regex.Pattern.quote; 44 45 /** 46 * Supports proxy settings using system properties This proxy selector 47 * provides backward compatibility with the old http protocol handler 48 * as far as how proxy is set 49 * 50 * Most of the implementation copied from the old http protocol handler 51 * 52 * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort, 53 * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks. 54 * NOTE: need to do gopher as well 55 */ 56 public class DefaultProxySelector extends ProxySelector { 57 58 /** 59 * This is where we define all the valid System Properties we have to 60 * support for each given protocol. 61 * The format of this 2 dimensional array is : 62 * - 1 row per protocol (http, ftp, ...) 63 * - 1st element of each row is the protocol name 64 * - subsequent elements are prefixes for Host & Port properties 65 * listed in order of priority. 66 * Example: 67 * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"}, 68 * means for FTP we try in that oder: 69 * + ftp.proxyHost & ftp.proxyPort 70 * + ftpProxyHost & ftpProxyPort 71 * + proxyHost & proxyPort 72 * + socksProxyHost & socksProxyPort 73 * 74 * Note that the socksProxy should *always* be the last on the list 75 */ 76 final static String[][] props = { 77 /* 78 * protocol, Property prefix 1, Property prefix 2, ... 79 */ 80 {"http", "http.proxy", "proxy", "socksProxy"}, 81 {"https", "https.proxy", "proxy", "socksProxy"}, 82 {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"}, 83 {"gopher", "gopherProxy", "socksProxy"}, 84 {"socket", "socksProxy"} 85 }; 86 87 private static final String SOCKS_PROXY_VERSION = "socksProxyVersion"; 88 89 private static boolean hasSystemProxies = false; 90 91 // Android-removed: Nonfunctional init logic: "net" library does not exist on Android. 92 /* 93 static { 94 final String key = "java.net.useSystemProxies"; 95 Boolean b = AccessController.doPrivileged( 96 new PrivilegedAction<Boolean>() { 97 public Boolean run() { 98 return NetProperties.getBoolean(key); 99 }}); 100 if (b != null && b.booleanValue()) { 101 java.security.AccessController.doPrivileged( 102 new java.security.PrivilegedAction<Void>() { 103 public Void run() { 104 System.loadLibrary("net"); 105 return null; 106 } 107 }); 108 hasSystemProxies = init(); 109 } 110 } 111 */ 112 113 /** 114 * How to deal with "non proxy hosts": 115 * since we do have to generate a pattern we don't want to do that if 116 * it's not necessary. Therefore we do cache the result, on a per-protocol 117 * basis, and change it only when the "source", i.e. the system property, 118 * did change. 119 */ 120 121 static class NonProxyInfo { 122 // Default value for nonProxyHosts, this provides backward compatibility 123 // by excluding localhost and its litteral notations. 124 static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]"; 125 126 String hostsSource; 127 Pattern pattern; 128 final String property; 129 final String defaultVal; 130 static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal); 131 static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal); 132 static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal); 133 // Android-changed: Different NonProxyInfo flags for https hosts vs. http. 134 static NonProxyInfo httpsNonProxyInfo = new NonProxyInfo("https.nonProxyHosts", null, null, defStringVal); 135 136 NonProxyInfo(String p, String s, Pattern pattern, String d) { 137 property = p; 138 hostsSource = s; 139 this.pattern = pattern; 140 defaultVal = d; 141 } 142 } 143 144 145 /** 146 * select() method. Where all the hard work is done. 147 * Build a list of proxies depending on URI. 148 * Since we're only providing compatibility with the system properties 149 * from previous releases (see list above), that list will always 150 * contain 1 single proxy, default being NO_PROXY. 151 */ 152 public java.util.List<Proxy> select(URI uri) { 153 if (uri == null) { 154 throw new IllegalArgumentException("URI can't be null."); 155 } 156 String protocol = uri.getScheme(); 157 String host = uri.getHost(); 158 159 if (host == null) { 160 // This is a hack to ensure backward compatibility in two 161 // cases: 1. hostnames contain non-ascii characters, 162 // internationalized domain names. in which case, URI will 163 // return null, see BugID 4957669; 2. Some hostnames can 164 // contain '_' chars even though it's not supposed to be 165 // legal, in which case URI will return null for getHost, 166 // but not for getAuthority() See BugID 4913253 167 String auth = uri.getAuthority(); 168 if (auth != null) { 169 int i; 170 i = auth.indexOf('@'); 171 if (i >= 0) { 172 auth = auth.substring(i+1); 173 } 174 i = auth.lastIndexOf(':'); 175 if (i >= 0) { 176 auth = auth.substring(0,i); 177 } 178 host = auth; 179 } 180 } 181 182 if (protocol == null || host == null) { 183 throw new IllegalArgumentException("protocol = "+protocol+" host = "+host); 184 } 185 List<Proxy> proxyl = new ArrayList<Proxy>(1); 186 187 NonProxyInfo pinfo = null; 188 189 if ("http".equalsIgnoreCase(protocol)) { 190 pinfo = NonProxyInfo.httpNonProxyInfo; 191 } else if ("https".equalsIgnoreCase(protocol)) { 192 // HTTPS uses the same property as HTTP, for backward 193 // compatibility 194 // Android-changed: Different NonProxyInfo flags for https hosts vs. http. 195 // pinfo = NonProxyInfo.httpNonProxyInfo; 196 pinfo = NonProxyInfo.httpsNonProxyInfo; 197 } else if ("ftp".equalsIgnoreCase(protocol)) { 198 pinfo = NonProxyInfo.ftpNonProxyInfo; 199 } else if ("socket".equalsIgnoreCase(protocol)) { 200 pinfo = NonProxyInfo.socksNonProxyInfo; 201 } 202 203 /** 204 * Let's check the System properties for that protocol 205 */ 206 final String proto = protocol; 207 final NonProxyInfo nprop = pinfo; 208 final String urlhost = host.toLowerCase(); 209 210 /** 211 * This is one big doPrivileged call, but we're trying to optimize 212 * the code as much as possible. Since we're checking quite a few 213 * System properties it does help having only 1 call to doPrivileged. 214 * Be mindful what you do in here though! 215 */ 216 Proxy p = AccessController.doPrivileged( 217 new PrivilegedAction<Proxy>() { 218 public Proxy run() { 219 int i, j; 220 String phost = null; 221 int pport = 0; 222 String nphosts = null; 223 InetSocketAddress saddr = null; 224 225 // Then let's walk the list of protocols in our array 226 for (i=0; i<props.length; i++) { 227 if (props[i][0].equalsIgnoreCase(proto)) { 228 for (j = 1; j < props[i].length; j++) { 229 /* System.getProp() will give us an empty 230 * String, "" for a defined but "empty" 231 * property. 232 */ 233 phost = NetProperties.get(props[i][j]+"Host"); 234 if (phost != null && phost.length() != 0) 235 break; 236 } 237 if (phost == null || phost.length() == 0) { 238 /** 239 * No system property defined for that 240 * protocol. Let's check System Proxy 241 * settings (Gnome & Windows) if we were 242 * instructed to. 243 */ 244 // Android-removed: Dead code, hasSystemProxies is always false. 245 /* 246 if (hasSystemProxies) { 247 String sproto; 248 if (proto.equalsIgnoreCase("socket")) 249 sproto = "socks"; 250 else 251 sproto = proto; 252 Proxy sproxy = getSystemProxy(sproto, urlhost); 253 if (sproxy != null) { 254 return sproxy; 255 } 256 } 257 */ 258 return Proxy.NO_PROXY; 259 } 260 // If a Proxy Host is defined for that protocol 261 // Let's get the NonProxyHosts property 262 if (nprop != null) { 263 nphosts = NetProperties.get(nprop.property); 264 synchronized (nprop) { 265 if (nphosts == null) { 266 if (nprop.defaultVal != null) { 267 nphosts = nprop.defaultVal; 268 } else { 269 nprop.hostsSource = null; 270 nprop.pattern = null; 271 } 272 } else if (nphosts.length() != 0) { 273 // add the required default patterns 274 // but only if property no set. If it 275 // is empty, leave empty. 276 nphosts += "|" + NonProxyInfo 277 .defStringVal; 278 } 279 if (nphosts != null) { 280 if (!nphosts.equals(nprop.hostsSource)) { 281 nprop.pattern = toPattern(nphosts); 282 nprop.hostsSource = nphosts; 283 } 284 } 285 if (shouldNotUseProxyFor(nprop.pattern, urlhost)) { 286 return Proxy.NO_PROXY; 287 } 288 } 289 } 290 // We got a host, let's check for port 291 292 pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue(); 293 if (pport == 0 && j < (props[i].length - 1)) { 294 // Can't find a port with same prefix as Host 295 // AND it's not a SOCKS proxy 296 // Let's try the other prefixes for that proto 297 for (int k = 1; k < (props[i].length - 1); k++) { 298 if ((k != j) && (pport == 0)) 299 pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue(); 300 } 301 } 302 303 // Still couldn't find a port, let's use default 304 if (pport == 0) { 305 if (j == (props[i].length - 1)) // SOCKS 306 pport = defaultPort("socket"); 307 else 308 pport = defaultPort(proto); 309 } 310 // We did find a proxy definition. 311 // Let's create the address, but don't resolve it 312 // as this will be done at connection time 313 saddr = InetSocketAddress.createUnresolved(phost, pport); 314 // Socks is *always* the last on the list. 315 if (j == (props[i].length - 1)) { 316 int version = NetProperties.getInteger(SOCKS_PROXY_VERSION, 5).intValue(); 317 return SocksProxy.create(saddr, version); 318 } else { 319 return new Proxy(Proxy.Type.HTTP, saddr); 320 } 321 } 322 } 323 return Proxy.NO_PROXY; 324 }}); 325 326 proxyl.add(p); 327 328 /* 329 * If no specific property was set for that URI, we should be 330 * returning an iterator to an empty List. 331 */ 332 return proxyl; 333 } 334 335 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 336 if (uri == null || sa == null || ioe == null) { 337 throw new IllegalArgumentException("Arguments can't be null."); 338 } 339 // ignored 340 } 341 342 343 private int defaultPort(String protocol) { 344 if ("http".equalsIgnoreCase(protocol)) { 345 return 80; 346 } else if ("https".equalsIgnoreCase(protocol)) { 347 return 443; 348 } else if ("ftp".equalsIgnoreCase(protocol)) { 349 return 80; 350 } else if ("socket".equalsIgnoreCase(protocol)) { 351 return 1080; 352 } else if ("gopher".equalsIgnoreCase(protocol)) { 353 return 80; 354 } else { 355 return -1; 356 } 357 } 358 359 // Android-removed: Native logic not available/used on Android. 360 /* 361 private native static boolean init(); 362 private synchronized native Proxy getSystemProxy(String protocol, String host); 363 */ 364 365 /** 366 * @return {@code true} if given this pattern for non-proxy hosts and this 367 * urlhost the proxy should NOT be used to access this urlhost 368 */ 369 static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) { 370 if (pattern == null || urlhost.isEmpty()) 371 return false; 372 boolean matches = pattern.matcher(urlhost).matches(); 373 return matches; 374 } 375 376 /** 377 * @param mask non-null mask 378 * @return {@link java.util.regex.Pattern} corresponding to this mask 379 * or {@code null} in case mask should not match anything 380 */ 381 static Pattern toPattern(String mask) { 382 boolean disjunctionEmpty = true; 383 StringJoiner joiner = new StringJoiner("|"); 384 for (String disjunct : mask.split("\\|")) { 385 if (disjunct.isEmpty()) 386 continue; 387 disjunctionEmpty = false; 388 String regex = disjunctToRegex(disjunct.toLowerCase()); 389 joiner.add(regex); 390 } 391 return disjunctionEmpty ? null : Pattern.compile(joiner.toString()); 392 } 393 394 /** 395 * @param disjunct non-null mask disjunct 396 * @return java regex string corresponding to this mask 397 */ 398 static String disjunctToRegex(String disjunct) { 399 String regex; 400 if (disjunct.startsWith("*")) { 401 regex = ".*" + quote(disjunct.substring(1)); 402 } else if (disjunct.endsWith("*")) { 403 regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*"; 404 } else { 405 regex = quote(disjunct); 406 } 407 return regex; 408 } 409 } 410