/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.file;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.log.Logger;
import com.google.common.io.BaseEncoding;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HttpsURLConnection;
/** Download a file over HTTP. */
public class HttpDownloader implements Downloader, AuthAwareDownloader {
private static final int PROGRESS_REPORT_EVERY_N_BYTES = 1000;
private static final Logger LOG = Logger.get(HttpDownloader.class);
private final Optional<Proxy> proxy;
public HttpDownloader() {
this(Optional.empty());
}
public HttpDownloader(Optional<Proxy> proxy) {
this.proxy = proxy;
}
@Override
public boolean fetch(BuckEventBus eventBus, URI uri, Path output) throws IOException {
return fetch(eventBus, uri, Optional.empty(), output);
}
@Override
public boolean fetch(
BuckEventBus eventBus, URI uri, Optional<PasswordAuthentication> authentication, Path output)
throws IOException {
if (!("https".equals(uri.getScheme()) || "http".equals(uri.getScheme()))) {
return false;
}
DownloadEvent.Started started = DownloadEvent.started(uri);
eventBus.post(started);
try {
HttpURLConnection connection = createConnection(uri);
if (authentication.isPresent()) {
if ("https".equals(uri.getScheme()) && connection instanceof HttpsURLConnection) {
PasswordAuthentication p = authentication.get();
String authStr = p.getUserName() + ":" + new String(p.getPassword());
String authEncoded =
BaseEncoding.base64().encode(authStr.getBytes(StandardCharsets.UTF_8));
connection.addRequestProperty("Authorization", "Basic " + authEncoded);
} else {
LOG.info("Refusing to send basic authentication over plain http.");
return false;
}
}
if (HttpURLConnection.HTTP_OK != connection.getResponseCode()) {
LOG.info("Unable to download %s: %s", uri, connection.getResponseMessage());
return false;
}
long contentLength = connection.getContentLengthLong();
try (InputStream is = new BufferedInputStream(connection.getInputStream());
OutputStream os = new BufferedOutputStream(Files.newOutputStream(output))) {
long read = 0;
while (true) {
int r = is.read();
read++;
if (r == -1) {
break;
}
if (read % PROGRESS_REPORT_EVERY_N_BYTES == 0) {
eventBus.post(new DownloadProgressEvent(uri, contentLength, read));
}
os.write(r);
}
}
return true;
} finally {
eventBus.post(DownloadEvent.finished(started));
}
}
protected HttpURLConnection createConnection(URI uri) throws IOException {
HttpURLConnection connection;
if (proxy.isPresent()) {
connection = (HttpURLConnection) uri.toURL().openConnection(proxy.get());
} else {
connection = (HttpURLConnection) uri.toURL().openConnection();
}
connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(20));
connection.setInstanceFollowRedirects(true);
return connection;
}
}