blob: dbe10aebbe84430daf58873c287e79dd61a75a86 [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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.
package com.google.devtools.build.lib.bazel.repository.downloader;
import com.google.common.base.Strings;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/**
* Helper class for setting up a proxy server for network communication
*/
public class ProxyHelper {
private final Map<String, String> env;
/**
* Creates new instance.
*
* @param env client environment to check for proxy settings
*/
public ProxyHelper(Map<String, String> env) {
this.env = env;
}
/**
* This method takes a String for the resource being requested and sets up a proxy to make
* the request if HTTP_PROXY and/or HTTPS_PROXY environment variables are set.
*
* @param requestedUrl remote resource that may need to be retrieved through a proxy
*/
public Proxy createProxyIfNeeded(URL requestedUrl) throws IOException {
String proxyAddress = null;
String noProxyUrl = env.get("no_proxy");
if (Strings.isNullOrEmpty(noProxyUrl)) {
noProxyUrl = env.get("NO_PROXY");
}
if (!Strings.isNullOrEmpty(noProxyUrl)) {
String[] noProxyUrlArray = noProxyUrl.split("\\s*,\\s*");
String requestedHost = requestedUrl.getHost();
for (int i = 0; i < noProxyUrlArray.length; i++) {
if (noProxyUrlArray[i].startsWith(".")) {
// This entry applies to sub-domains only.
if (requestedHost.endsWith(noProxyUrlArray[i])) {
return Proxy.NO_PROXY;
}
} else {
// This entry applies to the literal hostname and sub-domains.
if (requestedHost.equals(noProxyUrlArray[i])
|| requestedHost.endsWith("." + noProxyUrlArray[i])) {
return Proxy.NO_PROXY;
}
}
}
}
if (HttpUtils.isProtocol(requestedUrl, "https")) {
proxyAddress = env.get("https_proxy");
if (Strings.isNullOrEmpty(proxyAddress)) {
proxyAddress = env.get("HTTPS_PROXY");
}
} else if (HttpUtils.isProtocol(requestedUrl, "http")) {
proxyAddress = env.get("http_proxy");
if (Strings.isNullOrEmpty(proxyAddress)) {
proxyAddress = env.get("HTTP_PROXY");
}
}
return createProxy(proxyAddress);
}
/**
* This method takes a proxyAddress as a String (ex.
* http://userId:password@proxyhost.domain.com:8000) and sets JVM arguments for http and https
* proxy as well as returns a java.net.Proxy object for optional use.
*
* @param proxyAddress The fully qualified address of the proxy server
* @return Proxy
* @throws IOException
*/
public static Proxy createProxy(@Nullable String proxyAddress) throws IOException {
if (Strings.isNullOrEmpty(proxyAddress)) {
return Proxy.NO_PROXY;
}
// Here there be dragons.
Pattern urlPattern =
Pattern.compile("^(https?)://(([^:@]+?)(?::([^@]+?))?@)?([^:]+)(?::(\\d+))?/?$");
Matcher matcher = urlPattern.matcher(proxyAddress);
if (!matcher.matches()) {
throw new IOException("Proxy address " + proxyAddress + " is not a valid URL");
}
final String protocol = matcher.group(1);
final String idAndPassword = matcher.group(2);
final String username = matcher.group(3);
final String password = matcher.group(4);
final String hostname = matcher.group(5);
final String portRaw = matcher.group(6);
String cleanProxyAddress = proxyAddress;
if (idAndPassword != null) {
cleanProxyAddress =
proxyAddress.replace(idAndPassword, ""); // Used to remove id+pwd from logging
}
boolean https;
switch (protocol) {
case "https":
https = true;
break;
case "http":
https = false;
break;
default:
throw new IOException("Invalid proxy protocol for " + cleanProxyAddress);
}
int port = https ? 443 : 80; // Default port numbers
if (portRaw != null) {
try {
port = Integer.parseInt(portRaw);
} catch (NumberFormatException e) {
throw new IOException("Error parsing proxy port: " + cleanProxyAddress, e);
}
}
if (username != null) {
if (password == null) {
throw new IOException("No password given for proxy " + cleanProxyAddress);
}
// We need to make sure the proxy password is not url encoded; some special characters in
// proxy passwords require url encoding for shells and other tools to properly consume.
final String decodedPassword = URLDecoder.decode(password, "UTF-8");
Authenticator.setDefault(
new Authenticator() {
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, decodedPassword.toCharArray());
}
});
}
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(hostname, port));
}
}