Fix problems with retrieving external deps behind a proxy
Fixes #587 by properly setting up the Proxy object for HttpDownloader and by setting JVM arguments for GitCloner.
--
Reviewed-on: https://github.com/bazelbuild/bazel/pull/795
MOS_MIGRATED_REVID=115857070
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloner.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloner.java
index ecc7f8d..b862cf5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloner.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloner.java
@@ -114,6 +114,15 @@
mapper.get("init_submodules", Type.BOOLEAN),
outputDirectory);
+ // Setup proxy if remote is http or https
+ if (descriptor.remote != null && descriptor.remote.startsWith("http")) {
+ try {
+ ProxyHelper.createProxyIfNeeded(descriptor.remote);
+ } catch (IOException ie) {
+ throw new RepositoryFunctionException(ie, Transience.TRANSIENT);
+ }
+ }
+
Git git = null;
try {
if (descriptor.directory.exists()) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/HttpDownloader.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/HttpDownloader.java
index 3594767a..147bb45 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/HttpDownloader.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/HttpDownloader.java
@@ -14,8 +14,6 @@
package com.google.devtools.build.lib.bazel.repository;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
@@ -33,11 +31,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.Authenticator;
import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
import java.net.MalformedURLException;
-import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
@@ -46,8 +41,6 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -241,7 +234,7 @@
public static HttpConnection createAndConnect(URL url) throws IOException {
int retries = MAX_REDIRECTS;
- Proxy proxy = createProxyIfNeeded(url.getProtocol());
+ Proxy proxy = ProxyHelper.createProxyIfNeeded(url.toString());
do {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
try {
@@ -310,76 +303,6 @@
}
}
- private static Proxy createProxyIfNeeded(String protocol) throws IOException {
- if (protocol.equals("https")) {
- return createProxy(System.getenv("HTTPS_PROXY"));
- } else if (protocol.equals("http")) {
- return createProxy(System.getenv("HTTP_PROXY"));
- }
- return Proxy.NO_PROXY;
- }
-
- @VisibleForTesting
- static Proxy createProxy(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");
- }
-
- String protocol = matcher.group(1);
- final String username = matcher.group(2);
- final String password = matcher.group(3);
- String port = matcher.group(4);
-
- boolean https;
- switch (protocol) {
- case "https":
- https = true;
- break;
- case "http":
- https = false;
- break;
- default:
- throw new IOException("Invalid proxy protocol for " + proxyAddress);
- }
-
- if (username != null) {
- if (password == null) {
- throw new IOException("No password given for proxy " + proxyAddress);
- }
- System.setProperty(protocol + ".proxyUser", username);
- System.setProperty(protocol + ".proxyPassword", password);
-
- Authenticator.setDefault(
- new Authenticator() {
- @Override
- public PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(username, password.toCharArray());
- }
- });
- }
-
- if (port == null) {
- return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress, https ? 443 : 80));
- }
-
- try {
- return new Proxy(
- Proxy.Type.HTTP,
- new InetSocketAddress(
- proxyAddress.substring(0, proxyAddress.lastIndexOf(':')), Integer.parseInt(port)));
- } catch (NumberFormatException e) {
- throw new IOException("Error parsing proxy port: " + proxyAddress);
- }
- }
-
public static String getHash(Hasher hasher, Path path) throws IOException {
byte byteBuffer[] = new byte[BUFFER_SIZE];
try (InputStream stream = path.getInputStream()) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/ProxyHelper.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/ProxyHelper.java
new file mode 100644
index 0000000..0cb3772
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/ProxyHelper.java
@@ -0,0 +1,139 @@
+// 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;
+
+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.URLDecoder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class for setting up a proxy server for network communication
+ *
+ */
+public class ProxyHelper {
+
+ /**
+ * 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 The url for the remote resource that may need to be retrieved through a
+ * proxy
+ * @return Proxy
+ * @throws IOException
+ */
+ public static Proxy createProxyIfNeeded(String requestedUrl) throws IOException {
+ String lcUrl = requestedUrl.toLowerCase();
+ if (lcUrl.startsWith("https")) {
+ return createProxy(System.getenv("HTTPS_PROXY"));
+ } else if (lcUrl.startsWith("http")) {
+ return createProxy(System.getenv("HTTP_PROXY"));
+ }
+ return Proxy.NO_PROXY;
+ }
+
+ /**
+ * 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(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);
+ }
+ }
+
+ // We need to set both of these because jgit uses whichever the resource dictates
+ System.setProperty("https.proxyHost", hostname);
+ System.setProperty("https.proxyPort", Integer.toString(port));
+ System.setProperty("http.proxyHost", hostname);
+ System.setProperty("http.proxyPort", Integer.toString(port));
+
+ 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");
+ System.setProperty("http.proxyUser", username);
+ System.setProperty("http.proxyPassword", decodedPassword);
+ System.setProperty("https.proxyUser", username);
+ System.setProperty("https.proxyPassword", decodedPassword);
+
+ Authenticator.setDefault(
+ new Authenticator() {
+ @Override
+ public PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, decodedPassword.toCharArray());
+ }
+ });
+ }
+
+ return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(hostname, port));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/HttpDownloaderTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/HttpDownloaderTest.java
deleted file mode 100644
index c30ec58..0000000
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/HttpDownloaderTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2015 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;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-import java.net.Proxy;
-
-/**
- * Tests for @{link HttpDownloader}.
- */
-@RunWith(JUnit4.class)
-public class HttpDownloaderTest {
- @Test
- public void testNoProxy() throws Exception {
- // Empty address.
- Proxy proxy = HttpDownloader.createProxy(null);
- assertEquals(Proxy.NO_PROXY, proxy);
- proxy = HttpDownloader.createProxy("");
- assertEquals(Proxy.NO_PROXY, proxy);
- }
-
- @Test
- public void testProxyDefaultPort() throws Exception {
- Proxy proxy = HttpDownloader.createProxy("http://my.example.com");
- assertEquals(Proxy.Type.HTTP, proxy.type());
- assertThat(proxy.toString()).endsWith(":80");
-
- proxy = HttpDownloader.createProxy("https://my.example.com");
- assertThat(proxy.toString()).endsWith(":443");
- }
-
- @Test
- public void testProxyExplicitPort() throws Exception {
- Proxy proxy = HttpDownloader.createProxy("http://my.example.com:12345");
- assertThat(proxy.toString()).endsWith(":12345");
-
- proxy = HttpDownloader.createProxy("https://my.example.com:12345");
- assertThat(proxy.toString()).endsWith(":12345");
- }
-
- @Test
- public void testProxyNoProtocol() throws Exception {
- try {
- HttpDownloader.createProxy("my.example.com");
- fail("Expected protocol error");
- } catch (IOException e) {
- assertThat(e.getMessage()).contains("Proxy address my.example.com is not a valid URL");
- }
- }
-
- @Test
- public void testProxyNoProtocolWithPort() throws Exception {
- try {
- HttpDownloader.createProxy("my.example.com:12345");
- fail("Expected protocol error");
- } catch (IOException e) {
- assertThat(e.getMessage()).contains("Proxy address my.example.com:12345 is not a valid URL");
- }
- }
-
- @Test
- public void testProxyPortParsingError() throws Exception {
- try {
- HttpDownloader.createProxy("http://my.example.com:foo");
- fail("Should have thrown an error for invalid port");
- } catch (IOException e) {
- assertThat(e.getMessage())
- .contains("Proxy address http://my.example.com:foo is not a valid URL");
- }
- }
-
- @Test
- public void testProxyAuth() throws Exception {
- Proxy proxy = HttpDownloader.createProxy("http://foo:barbaz@my.example.com");
- assertEquals(Proxy.Type.HTTP, proxy.type());
- assertThat(proxy.toString()).endsWith(":80");
- assertEquals(System.getProperty("http.proxyUser"), "foo");
- assertEquals(System.getProperty("http.proxyPassword"), "barbaz");
-
- proxy = HttpDownloader.createProxy("https://biz:bat@my.example.com");
- assertThat(proxy.toString()).endsWith(":443");
- assertEquals(System.getProperty("https.proxyUser"), "biz");
- assertEquals(System.getProperty("https.proxyPassword"), "bat");
- }
-
- @Test
- public void testInvalidAuth() throws Exception {
- try {
- HttpDownloader.createProxy("http://foo@my.example.com");
- fail("Should have thrown an error for invalid auth");
- } catch (IOException e) {
- assertThat(e.getMessage()).contains("No password given for proxy");
- }
- }
-
-}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/ProxyHelperTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/ProxyHelperTest.java
new file mode 100644
index 0000000..a1a9142
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/ProxyHelperTest.java
@@ -0,0 +1,139 @@
+// Copyright 2015 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.net.Proxy;
+
+/**
+ * Tests for @{link ProxyHelper}.
+ */
+@RunWith(JUnit4.class)
+public class ProxyHelperTest {
+
+ @Test
+ public void testNoProxy() throws Exception {
+ // Empty address.
+ Proxy proxy = ProxyHelper.createProxy(null);
+ assertEquals(Proxy.NO_PROXY, proxy);
+ proxy = ProxyHelper.createProxy("");
+ assertEquals(Proxy.NO_PROXY, proxy);
+ }
+
+ @Test
+ public void testProxyDefaultPort() throws Exception {
+ Proxy proxy = ProxyHelper.createProxy("http://my.example.com");
+ assertEquals(Proxy.Type.HTTP, proxy.type());
+ assertThat(proxy.toString()).endsWith(":80");
+ assertEquals(System.getProperty("http.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("http.proxyPort"), "80");
+
+ proxy = ProxyHelper.createProxy("https://my.example.com");
+ assertThat(proxy.toString()).endsWith(":443");
+ assertEquals(System.getProperty("https.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("https.proxyPort"), "443");
+ }
+
+ @Test
+ public void testProxyExplicitPort() throws Exception {
+ Proxy proxy = ProxyHelper.createProxy("http://my.example.com:12345");
+ assertThat(proxy.toString()).endsWith(":12345");
+ assertEquals(System.getProperty("http.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("http.proxyPort"), "12345");
+
+ proxy = ProxyHelper.createProxy("https://my.example.com:12345");
+ assertThat(proxy.toString()).endsWith(":12345");
+ assertEquals(System.getProperty("https.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("https.proxyPort"), "12345");
+ }
+
+ @Test
+ public void testProxyNoProtocol() throws Exception {
+ try {
+ ProxyHelper.createProxy("my.example.com");
+ fail("Expected protocol error");
+ } catch (IOException e) {
+ assertThat(e.getMessage()).contains("Proxy address my.example.com is not a valid URL");
+ }
+ }
+
+ @Test
+ public void testProxyNoProtocolWithPort() throws Exception {
+ try {
+ ProxyHelper.createProxy("my.example.com:12345");
+ fail("Expected protocol error");
+ } catch (IOException e) {
+ assertThat(e.getMessage()).contains("Proxy address my.example.com:12345 is not a valid URL");
+ }
+ }
+
+ @Test
+ public void testProxyPortParsingError() throws Exception {
+ try {
+ ProxyHelper.createProxy("http://my.example.com:foo");
+ fail("Should have thrown an error for invalid port");
+ } catch (IOException e) {
+ assertThat(e.getMessage())
+ .contains("Proxy address http://my.example.com:foo is not a valid URL");
+ }
+ }
+
+ @Test
+ public void testProxyAuth() throws Exception {
+ Proxy proxy = ProxyHelper.createProxy("http://foo:barbaz@my.example.com");
+ assertEquals(Proxy.Type.HTTP, proxy.type());
+ assertThat(proxy.toString()).endsWith(":80");
+ assertEquals(System.getProperty("http.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("http.proxyPort"), "80");
+ assertEquals(System.getProperty("http.proxyUser"), "foo");
+ assertEquals(System.getProperty("http.proxyPassword"), "barbaz");
+
+ proxy = ProxyHelper.createProxy("https://biz:bat@my.example.com");
+ assertThat(proxy.toString()).endsWith(":443");
+ assertEquals(System.getProperty("https.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("https.proxyPort"), "443");
+ assertEquals(System.getProperty("https.proxyUser"), "biz");
+ assertEquals(System.getProperty("https.proxyPassword"), "bat");
+ }
+
+ @Test
+ public void testEncodedProxyAuth() throws Exception {
+ Proxy proxy = ProxyHelper.createProxy("http://foo:b%40rb%40z@my.example.com");
+ assertEquals(Proxy.Type.HTTP, proxy.type());
+ assertThat(proxy.toString()).endsWith(":80");
+ assertEquals(System.getProperty("http.proxyHost"), "my.example.com");
+ assertEquals(System.getProperty("http.proxyPort"), "80");
+ assertEquals(System.getProperty("http.proxyUser"), "foo");
+ assertEquals(System.getProperty("http.proxyPassword"), "b@rb@z");
+ }
+
+ @Test
+ public void testInvalidAuth() throws Exception {
+ try {
+ ProxyHelper.createProxy("http://foo@my.example.com");
+ fail("Should have thrown an error for invalid auth");
+ } catch (IOException e) {
+ assertThat(e.getMessage()).contains("No password given for proxy");
+ }
+ }
+}