aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnection.java238
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpArchiveRule.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpFileRule.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpJarRule.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewHttpArchiveRule.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD4
-rw-r--r--src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloaderTestSuite.java27
-rw-r--r--src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectionTest.java138
-rw-r--r--src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java254
-rwxr-xr-xsrc/test/shell/bazel/external_integration_test.sh29
11 files changed, 293 insertions, 422 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnection.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnection.java
deleted file mode 100644
index 84e6c47e63..0000000000
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnection.java
+++ /dev/null
@@ -1,238 +0,0 @@
-// 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.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
-import com.google.common.io.ByteStreams;
-import com.google.common.net.MediaType;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.Proxy;
-import java.net.SocketTimeoutException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-
-/**
- * Represents a connection over HTTP.
- */
-class HttpConnection implements Closeable {
- private static final int MAX_REDIRECTS = 20;
- private static final int TIMEOUT_MS = 60000;
- private final InputStream inputStream;
- private final int contentLength;
-
- private HttpConnection(InputStream inputStream, int contentLength) {
- this.inputStream = inputStream;
- this.contentLength = contentLength;
- }
-
- public InputStream getInputStream() {
- return inputStream;
- }
-
- /**
- * @return The length of the response, or -1 if unknown.
- */
- int getContentLength() {
- return contentLength;
- }
-
- @Override
- public void close() throws IOException {
- inputStream.close();
- }
-
- private static int parseContentLength(HttpURLConnection connection) {
- String length;
- try {
- length = connection.getHeaderField("Content-Length");
- if (length == null) {
- return -1;
- }
- return Integer.parseInt(length);
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-
- /**
- * Connects to the given URL. Should not leave any connections open if anything goes wrong.
- */
- static HttpConnection createAndConnect(URL url, Map<String, String> clientEnv)
- throws IOException {
- Proxy proxy = ProxyHelper.createProxyIfNeeded(url.toString(), clientEnv);
- for (int i = 0; i < MAX_REDIRECTS; ++i) {
- URLConnection urlConnection = url.openConnection(proxy);
- if (!(urlConnection instanceof HttpURLConnection)) {
- return createFileConnection(urlConnection);
- }
-
- HttpURLConnection connection = (HttpURLConnection) urlConnection;
- int statusCode;
- try {
- statusCode = createAndConnectViaHttp(connection);
- } catch (IOException e) {
- connection.disconnect();
- throw e;
- }
-
- switch (statusCode) {
- case HttpURLConnection.HTTP_OK:
- try {
- return new HttpConnection(connection.getInputStream(), parseContentLength(connection));
- } catch (IOException e) {
- connection.disconnect();
- throw e;
- }
- case HttpURLConnection.HTTP_MOVED_PERM:
- case HttpURLConnection.HTTP_MOVED_TEMP:
- // Try again with the new URL. This is the only case that doesn't return/throw.
- url = tryGetLocation(statusCode, connection);
- connection.disconnect();
- break;
- case -1:
- throw new IOException("An HTTP error occurred");
- default:
- throw new IOException(
- String.format(
- "%s %s: %s",
- connection.getResponseCode(),
- connection.getResponseMessage(),
- readBody(connection)));
- }
- }
- throw new IOException("Maximum redirects (" + MAX_REDIRECTS + ") exceeded");
- }
-
- // For file:// URLs.
- private static HttpConnection createFileConnection(URLConnection connection)
- throws IOException {
- int contentLength = connection.getContentLength();
- // check for empty file. -1 is a valid contentLength, meaning the size of unknown. It's a
- // common return value for an FTP download request for example. Local files will always
- // have a valid contentLength value.
- if (contentLength == 0) {
- throw new IOException("Attempted to download an empty file");
- }
-
- return new HttpConnection(connection.getInputStream(), contentLength);
- }
-
- private static int createAndConnectViaHttp(HttpURLConnection connection) throws IOException {
- connection.setConnectTimeout(TIMEOUT_MS);
- connection.setReadTimeout(TIMEOUT_MS);
- try {
- connection.connect();
- } catch (SocketTimeoutException e) {
- throw new IOException(
- "Timed out connecting to " + connection.getURL() + " : " + e.getMessage(), e);
- } catch (IllegalArgumentException | IOException e) {
- throw new IOException(
- "Failed to connect to " + connection.getURL() + " : " + e.getMessage(), e);
- }
- return connection.getResponseCode();
- }
-
- private static URL tryGetLocation(int statusCode, HttpURLConnection connection)
- throws IOException {
- String newLocation = connection.getHeaderField("Location");
- if (newLocation == null) {
- throw new IOException(
- "Remote returned " + statusCode + " but did not return location header.");
- }
-
- URL newUrl;
- try {
- newUrl = new URL(newLocation);
- } catch (MalformedURLException e) {
- throw new IOException("Remote returned invalid location header: " + newLocation);
- }
-
- String newProtocol = newUrl.getProtocol();
- if (!("http".equals(newProtocol) || "https".equals(newProtocol))) {
- throw new IOException(
- "Remote returned invalid location header: " + newLocation);
- }
-
- return newUrl;
- }
-
- /**
- * Attempts to detect the encoding the HTTP reponse is using.
- *
- * <p>This attempts to read the Content-Encoding header, then the Content-Type header,
- * then just falls back to UTF-8.</p>
- *
- * @throws IOException If something goes wrong (the encoding isn't parsable or is, but isn't
- * supported by the system).
- */
- @VisibleForTesting
- static Charset getEncoding(HttpURLConnection connection) throws IOException {
- String encoding = connection.getContentEncoding();
- if (encoding != null) {
- if (Charset.availableCharsets().containsKey(encoding)) {
- try {
- return Charset.forName(encoding);
- } catch (IllegalArgumentException | UnsupportedOperationException e) {
- throw new IOException(
- "Got invalid encoding from " + connection.getURL() + ": " + encoding);
- }
- } else {
- throw new IOException(
- "Got unavailable encoding from " + connection.getURL() + ": " + encoding);
- }
- }
- encoding = connection.getContentType();
- if (encoding == null) {
- return StandardCharsets.UTF_8;
- }
- try {
- MediaType mediaType = MediaType.parse(encoding);
- if (mediaType == null) {
- return StandardCharsets.UTF_8;
- }
- Optional<Charset> charset = mediaType.charset();
- if (charset.isPresent()) {
- return charset.get();
- }
- } catch (IllegalArgumentException | IllegalStateException e) {
- throw new IOException(
- "Got invalid encoding from " + connection.getURL() + ": " + encoding);
- }
- return StandardCharsets.UTF_8;
- }
-
- private static String readBody(HttpURLConnection connection) throws IOException {
- InputStream errorStream = connection.getErrorStream();
- Charset encoding = getEncoding(connection);
- if (errorStream != null) {
- return new String(ByteStreams.toByteArray(errorStream), encoding);
- }
-
- InputStream responseStream = connection.getInputStream();
- if (responseStream != null) {
- return new String(ByteStreams.toByteArray(responseStream), encoding);
- }
-
- return null;
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
index 400f9e2d02..ea4cc81d7c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
@@ -31,6 +31,7 @@ import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -82,10 +83,7 @@ public class HttpDownloader {
try {
return download(url, sha256, type, outputDirectory, eventHandler, clientEnv);
} catch (IOException e) {
- throw new RepositoryFunctionException(
- new IOException(
- "Error downloading from " + url + " to " + outputDirectory + ": " + e.getMessage()),
- SkyFunctionException.Transience.TRANSIENT);
+ throw new RepositoryFunctionException(e, SkyFunctionException.Transience.TRANSIENT);
}
}
@@ -130,10 +128,10 @@ public class HttpDownloader {
AtomicInteger totalBytes = new AtomicInteger(0);
final ScheduledFuture<?> loggerHandle = getLoggerHandle(totalBytes, eventHandler, urlString);
final URL url = new URL(urlString);
+ Proxy proxy = ProxyHelper.createProxyIfNeeded(url.toString(), clientEnv);
try (OutputStream out = destination.getOutputStream();
- HttpConnection connection = HttpConnection.createAndConnect(url, clientEnv)) {
- InputStream inputStream = connection.getInputStream();
+ InputStream inputStream = HttpConnector.connect(url, proxy, eventHandler)) {
int read;
byte[] buf = new byte[BUFFER_SIZE];
while ((read = inputStream.read(buf)) > 0) {
@@ -143,11 +141,6 @@ public class HttpDownloader {
throw new InterruptedException("Download interrupted");
}
}
- if (connection.getContentLength() != -1
- && totalBytes.get() != connection.getContentLength()) {
- throw new IOException("Expected " + formatSize(connection.getContentLength()) + ", got "
- + formatSize(totalBytes.get()));
- }
} catch (IOException e) {
throw new IOException(
"Error downloading " + url + " to " + destination + ": " + e.getMessage());
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpArchiveRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpArchiveRule.java
index bc5a9cdf20..0406fabf8e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpArchiveRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpArchiveRule.java
@@ -39,7 +39,7 @@ public class HttpArchiveRule implements RuleDefinition {
A URL referencing an archive file containing a Bazel repository.
<p>Archives of type .zip, .jar, .war, .tar.gz or .tgz are supported. There is no support
- for authentication. Redirections are followed, but not from HTTP to HTTPS.</p>
+ for authentication.</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("url", STRING).mandatory())
/* <!-- #BLAZE_RULE(http_archive).ATTRIBUTE(sha256) -->
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpFileRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpFileRule.java
index 97a8dc3ac5..7c96c4cc32 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpFileRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpFileRule.java
@@ -39,8 +39,7 @@ public class HttpFileRule implements RuleDefinition {
/* <!-- #BLAZE_RULE(http_file).ATTRIBUTE(url) -->
A URL to a file that will be made available to Bazel.
- <p>This must be an http or https URL. Authentication is not support.
- Redirections are followed, but not from HTTP to HTTPS.</p>
+ <p>This must be an http or https URL. Authentication is not supported.</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("url", STRING).mandatory())
/* <!-- #BLAZE_RULE(http_file).ATTRIBUTE(sha256) -->
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpJarRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpJarRule.java
index 82a49ef583..14c3b16177 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpJarRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/HttpJarRule.java
@@ -38,8 +38,7 @@ public class HttpJarRule implements RuleDefinition {
/* <!-- #BLAZE_RULE(http_jar).ATTRIBUTE(url) -->
A URL to an archive file containing a Bazel repository.
- <p>This must be an http or https URL that ends with .jar. Redirections are followed, but
- not from HTTP to HTTPS.</p>
+ <p>This must be an http or https URL that ends with .jar.</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("url", STRING).mandatory())
/* <!-- #BLAZE_RULE(http_jar).ATTRIBUTE(sha256) -->
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewHttpArchiveRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewHttpArchiveRule.java
index 70ddb28e21..30a5cd8b41 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewHttpArchiveRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewHttpArchiveRule.java
@@ -36,7 +36,7 @@ public class NewHttpArchiveRule implements RuleDefinition {
A URL referencing an archive file containing a Bazel repository.
<p>Archives of type .zip, .jar, .war, .tar.gz or .tgz are supported. There is no support
- for authentication. Redirections are followed, but not from HTTP to HTTPS.</p>
+ for authentication.</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("url", STRING).mandatory())
/* <!-- #BLAZE_RULE(new_http_archive).ATTRIBUTE(sha256) -->
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
index f29ceb4e7a..988efb8132 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
@@ -5,11 +5,11 @@ filegroup(
)
java_test(
- name = "DownloaderTests",
+ name = "DownloaderTestSuite",
srcs = glob(["*.java"]),
tags = ["rules"],
- test_class = "com.google.devtools.build.lib.AllTests",
deps = [
+ "//src/main/java/com/google/devtools/build/lib:events",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
"//src/test/java/com/google/devtools/build/lib:foundations_testutil",
"//src/test/java/com/google/devtools/build/lib:test_runner",
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloaderTestSuite.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloaderTestSuite.java
new file mode 100644
index 0000000000..1a48a1ceb5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloaderTestSuite.java
@@ -0,0 +1,27 @@
+// 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 org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/** Test suite for downloader package. */
+@RunWith(Suite.class)
+@SuiteClasses({
+ HttpConnectorTest.class,
+ ProxyHelperTest.class,
+})
+class DownloaderTestSuite {}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectionTest.java
deleted file mode 100644
index 93d9f61ddd..0000000000
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectionTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-// 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 static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.when;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.ByteStreams;
-import com.google.common.net.MediaType;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.nio.charset.Charset;
-import java.util.Map;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-/**
- * Tests for @{link HttpConnection}.
- */
-@RunWith(JUnit4.class)
-public class HttpConnectionTest {
-
- @Test
- public void testEncodingSet() throws Exception {
- Map<String, Charset> charsets = Charset.availableCharsets();
- assertThat(charsets).isNotEmpty();
- Map.Entry<String, Charset> entry = charsets.entrySet().iterator().next();
-
- String availableEncoding = entry.getKey();
- Charset availableCharset = entry.getValue();
-
- HttpURLConnection connection = Mockito.mock(HttpURLConnection.class);
- when(connection.getContentEncoding()).thenReturn(availableEncoding);
- Charset charset = HttpConnection.getEncoding(connection);
- assertEquals(availableCharset, charset);
- }
-
- @Test
- public void testInvalidEncoding() throws Exception {
- HttpURLConnection connection = Mockito.mock(HttpURLConnection.class);
- when(connection.getContentEncoding()).thenReturn("This-isn't-a-valid-content-encoding");
- try {
- HttpConnection.getEncoding(connection);
- fail("Expected exception");
- } catch (IOException e) {
- assertThat(e.getMessage()).contains("Got unavailable encoding");
- }
- }
-
- @Test
- public void testContentType() throws Exception {
- HttpURLConnection connection = Mockito.mock(HttpURLConnection.class);
- when(connection.getContentType()).thenReturn(MediaType.HTML_UTF_8.toString());
- Charset charset = HttpConnection.getEncoding(connection);
- assertEquals(UTF_8, charset);
- }
-
- @Test
- public void testInvalidContentType() throws Exception {
- HttpURLConnection connection = Mockito.mock(HttpURLConnection.class);
- when(connection.getContentType()).thenReturn("This-isn't-a-valid-content-type");
- try {
- HttpConnection.getEncoding(connection);
- fail("Expected exception");
- } catch (IOException e) {
- assertThat(e.getMessage()).contains("Got invalid encoding");
- }
- }
-
- @Test
- public void testNoEncodingNorContentType() throws Exception {
- HttpURLConnection connection = Mockito.mock(HttpURLConnection.class);
- Charset charset = HttpConnection.getEncoding(connection);
- assertEquals(UTF_8, charset);
- }
-
- /**
- * Creates a temporary file with the specified {@code fileContents}. The file will be
- * automatically deleted when the JVM exits.
- *
- * @param fileContents the contents of the file
- * @return the {@link File} object representing the temporary file
- */
- private static File createTempFile(byte[] fileContents) throws IOException {
- File temp = File.createTempFile("httpConnectionTest", ".tmp");
- temp.deleteOnExit();
- try (FileOutputStream outputStream = new FileOutputStream(temp)) {
- outputStream.write(fileContents);
- }
- return temp;
- }
-
- @Test
- public void testLocalFileDownload() throws Exception {
- byte[] fileContents = "this is a test".getBytes(UTF_8);
- File temp = createTempFile(fileContents);
- HttpConnection httpConnection =
- HttpConnection.createAndConnect(temp.toURI().toURL(), ImmutableMap.<String, String>of());
-
- assertThat(httpConnection.getContentLength()).isEqualTo(fileContents.length);
-
- byte[] readContents = ByteStreams.toByteArray(httpConnection.getInputStream());
- assertThat(readContents).isEqualTo(fileContents);
- }
-
- @Test
- public void testLocalEmptyFileDownload() throws Exception {
- byte[] fileContents = new byte[0];
- // create a temp file
- File temp = createTempFile(fileContents);
- try {
- HttpConnection.createAndConnect(temp.toURI().toURL(), ImmutableMap.<String, String>of());
- fail("Expected exception");
- } catch (IOException ex) {
- // expected
- }
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java
new file mode 100644
index 0000000000..fa5a1b6bb6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorTest.java
@@ -0,0 +1,254 @@
+// 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 static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+import com.google.devtools.build.lib.events.EventHandler;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Proxy;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link HttpConnector}.
+ */
+@RunWith(JUnit4.class)
+public class HttpConnectorTest {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final HttpURLConnection connection = mock(HttpURLConnection.class);
+ private final EventHandler eventHandler = mock(EventHandler.class);
+
+ @After
+ public void after() throws Exception {
+ executor.shutdownNow();
+ }
+
+ @Test
+ public void testLocalFileDownload() throws Exception {
+ byte[] fileContents = "this is a test".getBytes(UTF_8);
+ assertThat(
+ ByteStreams.toByteArray(
+ HttpConnector.connect(
+ createTempFile(fileContents).toURI().toURL(),
+ Proxy.NO_PROXY,
+ eventHandler)))
+ .isEqualTo(fileContents);
+ }
+
+ @Test
+ public void missingLocationInRedirect_throwsIOException() throws Exception {
+ thrown.expect(IOException.class);
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ HttpConnector.getLocation(connection);
+ }
+
+ @Test
+ public void absoluteLocationInRedirect_returnsNewUrl() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("http://new.example/hi");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("http://new.example/hi"));
+ }
+
+ @Test
+ public void redirectOnlyHasPath_mergesHostFromOriginalUrl() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("/hi");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("http://lol.example/hi"));
+ }
+
+ @Test
+ public void locationOnlyHasPathWithoutSlash_failsToMerge() throws Exception {
+ thrown.expect(IOException.class);
+ thrown.expectMessage("Could not merge");
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("omg");
+ HttpConnector.getLocation(connection);
+ }
+
+ @Test
+ public void locationHasFragment_prefersNewFragment() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://lol.example#a"));
+ when(connection.getHeaderField("Location")).thenReturn("http://new.example/hi#b");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("http://new.example/hi#b"));
+ }
+
+ @Test
+ public void locationHasNoFragmentButOriginalDoes_mergesOldFragment() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://lol.example#a"));
+ when(connection.getHeaderField("Location")).thenReturn("http://new.example/hi");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("http://new.example/hi#a"));
+ }
+
+ @Test
+ public void oldUrlHasPasswordRedirectingToSameDomain_mergesPassword() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://a:b@lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("http://lol.example/hi");
+ assertThat(HttpConnector.getLocation(connection))
+ .isEqualTo(new URL("http://a:b@lol.example/hi"));
+ when(connection.getURL()).thenReturn(new URL("http://a:b@lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("/hi");
+ assertThat(HttpConnector.getLocation(connection))
+ .isEqualTo(new URL("http://a:b@lol.example/hi"));
+ }
+
+ @Test
+ public void oldUrlHasPasswordRedirectingToNewServer_doesntMergePassword() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://a:b@lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("http://new.example/hi");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("http://new.example/hi"));
+ when(connection.getURL()).thenReturn(new URL("http://a:b@lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("http://lol.example:81/hi");
+ assertThat(HttpConnector.getLocation(connection))
+ .isEqualTo(new URL("http://lol.example:81/hi"));
+ }
+
+ @Test
+ public void redirectToFtp_throwsIOException() throws Exception {
+ thrown.expect(IOException.class);
+ thrown.expectMessage("Bad Location");
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("ftp://lol.example");
+ HttpConnector.getLocation(connection);
+ }
+
+ @Test
+ public void redirectToHttps_works() throws Exception {
+ when(connection.getURL()).thenReturn(new URL("http://lol.example"));
+ when(connection.getHeaderField("Location")).thenReturn("https://lol.example");
+ assertThat(HttpConnector.getLocation(connection)).isEqualTo(new URL("https://lol.example"));
+ }
+
+ @Test
+ public void testNormalRequest() throws Exception {
+ try (final ServerSocket server = new ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))) {
+ Future<Void> thread =
+ executor.submit(
+ new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ try (Socket socket = server.accept()) {
+ send(socket,
+ "HTTP/1.1 200 OK\r\n"
+ + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "Content-Length: 5\r\n"
+ + "\r\n"
+ + "hello");
+ }
+ return null;
+ }
+ });
+ try (Reader payload =
+ new InputStreamReader(
+ HttpConnector.connect(
+ new URL(String.format("http://127.0.0.1:%d", server.getLocalPort())),
+ Proxy.NO_PROXY,
+ eventHandler),
+ ISO_8859_1)) {
+ assertThat(CharStreams.toString(payload)).isEqualTo("hello");
+ }
+ thread.get();
+ }
+ }
+
+ @Test
+ public void testRetry() throws Exception {
+ try (final ServerSocket server = new ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))) {
+ Future<Void> thread =
+ executor.submit(
+ new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ try (Socket socket = server.accept()) {
+ send(socket,
+ "HTTP/1.1 500 Incredible Catastrophe\r\n"
+ + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "Content-Length: 8\r\n"
+ + "\r\n"
+ + "nononono");
+ }
+ try (Socket socket = server.accept()) {
+ send(socket,
+ "HTTP/1.1 200 OK\r\n"
+ + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "Content-Length: 5\r\n"
+ + "\r\n"
+ + "hello");
+ }
+ return null;
+ }
+ });
+ try (Reader payload =
+ new InputStreamReader(
+ HttpConnector.connect(
+ new URL(String.format("http://127.0.0.1:%d", server.getLocalPort())),
+ Proxy.NO_PROXY,
+ eventHandler),
+ ISO_8859_1)) {
+ assertThat(CharStreams.toString(payload)).isEqualTo("hello");
+ }
+ thread.get();
+ }
+ }
+
+ private static void send(Socket socket, String data) throws IOException {
+ ByteStreams.copy(
+ ByteSource.wrap(data.getBytes(ISO_8859_1)).openStream(),
+ socket.getOutputStream());
+ }
+
+ private File createTempFile(byte[] fileContents) throws IOException {
+ File temp = testFolder.newFile();
+ try (FileOutputStream outputStream = new FileOutputStream(temp)) {
+ outputStream.write(fileContents);
+ }
+ return temp;
+ }
+}
diff --git a/src/test/shell/bazel/external_integration_test.sh b/src/test/shell/bazel/external_integration_test.sh
index a78871f473..0f610e050c 100755
--- a/src/test/shell/bazel/external_integration_test.sh
+++ b/src/test/shell/bazel/external_integration_test.sh
@@ -345,7 +345,7 @@ EOF
kill_nc
# Observes that we tried to follow redirect, but failed due to ridiculous
# port.
- expect_log "Failed to connect.*port out of range"
+ expect_log "port out of range"
}
function test_http_404() {
@@ -361,7 +361,7 @@ http_file(
EOF
bazel build @toto//file &> $TEST_log && fail "Expected run to fail"
kill_nc
- expect_log "404 Not Found: Help, I'm lost!"
+ expect_log "404 Not Found"
}
# Tests downloading a file and using it as a dependency.
@@ -761,31 +761,6 @@ EOF
assert_contains "def" bazel-genfiles/external/x/catter.out
}
-function test_truncated() {
- http_response="$TEST_TMPDIR/http_response"
- cat > "$http_response" <<EOF
-HTTP/1.0 200 OK
-Content-length: 200
-
-EOF
- echo "foo" >> "$http_response"
- echo ${nc_port:=$(pick_random_unused_tcp_port)} > /dev/null
- nc_log="$TEST_TMPDIR/nc.log"
- nc_l "$nc_port" < "$http_response" >& "$nc_log" &
- nc_pid=$!
-
- cat > WORKSPACE <<EOF
-http_archive(
- name = "foo",
- url = "http://localhost:$nc_port",
- sha256 = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
-)
-EOF
- bazel build @foo//bar &> $TEST_log || echo "Build failed, as expected"
- expect_log "Expected 200B, got 4B"
-}
-
-
function test_android_sdk_basic_load() {
cat >> WORKSPACE <<'EOF' || fail "Couldn't cat"
android_sdk_repository(