switch all Downloader subclasses to use Uri instead of URL

java.net.URL barfs on custom URL schemes, and making it handle them is
really hard.  Basically, there needs to be a Handler stub class, then
URL.setURLStreamHandlerFactory() must run when F-Droid starts, since
it has to be set before any URL instance is used.  This all leaves
some weird logic that gives the false impression that URLConnection
will handle these custom schemes.

Switching to Uri/urlString throughout the code matches the other
classes that use urlString as the unique ID, and this doesn't add more
lines of code.
This commit is contained in:
Hans-Christoph Steiner 2018-03-29 22:26:41 +02:00
parent 195aaae7e5
commit df08e84e78
11 changed files with 40 additions and 84 deletions

View File

@ -60,12 +60,8 @@ import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.hiding.HidingManager;
import sun.net.www.protocol.bluetooth.Handler;
import java.io.IOException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.Security;
import java.util.List;
@ -370,16 +366,6 @@ public class FDroidApp extends Application {
}
});
// This is added so that the bluetooth:// scheme we use for URLs the BluetoothDownloader
// understands is not treated as invalid by the java.net.URL class. The actual Handler does
// nothing, but its presence is enough.
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return TextUtils.equals(protocol, "bluetooth") ? new Handler() : null;
}
});
final Context context = this;
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@Override

View File

@ -6,7 +6,6 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
@ -15,7 +14,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
@ -31,7 +29,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
@ -157,7 +154,7 @@ public class IndexV1Updater extends RepoUpdater {
JarFile jarFile = new JarFile(outputFile, true);
JarEntry indexEntry = (JarEntry) jarFile.getEntry(DATA_FILE_NAME);
InputStream indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
processIndexListener, new URL(repo.address), (int) indexEntry.getSize());
processIndexListener, repo.address, (int) indexEntry.getSize());
processIndexV1(indexInputStream, indexEntry, cacheTag);
}

View File

@ -3,12 +3,11 @@ package org.fdroid.fdroid;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
class ProgressBufferedInputStream extends BufferedInputStream {
private final ProgressListener progressListener;
private final URL sourceUrl;
private final String urlString;
private final int totalBytes;
private int currentBytes;
@ -17,10 +16,10 @@ class ProgressBufferedInputStream extends BufferedInputStream {
* Reports progress to the specified {@link ProgressListener}, with the
* progress based on the {@code totalBytes}.
*/
ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, URL sourceUrl, int totalBytes) {
ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, String urlString, int totalBytes) {
super(in);
this.progressListener = progressListener;
this.sourceUrl = sourceUrl;
this.urlString = urlString;
this.totalBytes = totalBytes;
}
@ -32,7 +31,7 @@ class ProgressBufferedInputStream extends BufferedInputStream {
* the digits changing because it looks pretty, < 9000 since the reads won't
* line up exactly */
if (currentBytes % 333333 < 9000) {
progressListener.onProgress(sourceUrl, currentBytes, totalBytes);
progressListener.onProgress(urlString, currentBytes, totalBytes);
}
}
return super.read(buffer, byteOffset, byteCount);

View File

@ -19,6 +19,6 @@ import java.net.URL;
*/
public interface ProgressListener {
void onProgress(URL sourceUrl, long bytesRead, long totalBytes);
void onProgress(String urlString, long bytesRead, long totalBytes);
}

View File

@ -53,7 +53,6 @@ import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
@ -213,7 +212,7 @@ public class RepoUpdater {
JarFile jarFile = new JarFile(downloadedFile, true);
JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
processIndexListener, new URL(repo.address), (int) indexEntry.getSize());
processIndexListener, repo.address, (int) indexEntry.getSize());
// Process the index...
SAXParserFactory factory = SAXParserFactory.newInstance();
@ -251,14 +250,14 @@ public class RepoUpdater {
protected final ProgressListener downloadListener = new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, long bytesRead, long totalBytes) {
public void onProgress(String urlString, long bytesRead, long totalBytes) {
UpdateService.reportDownloadProgress(context, RepoUpdater.this, bytesRead, totalBytes);
}
};
protected final ProgressListener processIndexListener = new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, long bytesRead, long totalBytes) {
public void onProgress(String urlString, long bytesRead, long totalBytes) {
UpdateService.reportProcessIndexProgress(context, RepoUpdater.this, bytesRead, totalBytes);
}
};

View File

@ -1,8 +1,8 @@
package org.fdroid.fdroid.net;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import org.apache.commons.io.input.BoundedInputStream;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
@ -14,7 +14,6 @@ import org.fdroid.fdroid.net.bluetooth.httpish.Response;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class BluetoothDownloader extends Downloader {
@ -24,10 +23,11 @@ public class BluetoothDownloader extends Downloader {
private FileDetails fileDetails;
private final String sourcePath;
public BluetoothDownloader(String macAddress, URL sourceUrl, File destFile) throws IOException {
super(sourceUrl, destFile);
public BluetoothDownloader(Uri uri, File destFile) throws IOException {
super(uri, destFile);
String macAddress = uri.getHost().replace("-", ":");
this.connection = new BluetoothClient(macAddress).openConnection();
this.sourcePath = sourceUrl.getPath();
this.sourcePath = uri.getPath();
}
@Override
@ -58,7 +58,7 @@ public class BluetoothDownloader extends Downloader {
if (fileDetails == null) {
Utils.debugLog(TAG, "Going to Bluetooth \"server\" to get file details.");
try {
fileDetails = Request.createHEAD(sourceUrl.getPath(), connection).send().toFileDetails();
fileDetails = Request.createHEAD(sourcePath, connection).send().toFileDetails();
} catch (IOException e) {
Log.e(TAG, "Error getting file details from Bluetooth \"server\"", e);
}

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.net;
import android.net.Uri;
import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.Utils;
@ -9,7 +10,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
@ -37,7 +37,7 @@ public abstract class Downloader {
public final File outputFile;
final URL sourceUrl;
final String urlString;
String cacheTag;
boolean notFound;
@ -52,8 +52,8 @@ public abstract class Downloader {
protected abstract void close();
Downloader(URL url, File destFile) {
this.sourceUrl = url;
Downloader(Uri uri, File destFile) {
this.urlString = uri.toString();
outputFile = destFile;
}
@ -201,7 +201,7 @@ public abstract class Downloader {
@Override
public void run() {
if (downloaderProgressListener != null) {
downloaderProgressListener.onProgress(sourceUrl, bytesRead, totalBytes);
downloaderProgressListener.onProgress(urlString, bytesRead, totalBytes);
}
}
};

View File

@ -2,19 +2,15 @@ package org.fdroid.fdroid.net;
import android.content.Context;
import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class DownloaderFactory {
private static LocalBroadcastManager localBroadcastManager;
/**
* Downloads to a temporary file, which *you must delete yourself when
* you are done. It is stored in {@link Context#getCacheDir()} and starts
@ -34,22 +30,19 @@ public class DownloaderFactory {
public static Downloader create(Context context, String urlString, File destFile)
throws IOException {
URL url = new URL(urlString);
Downloader downloader;
if (localBroadcastManager == null) {
localBroadcastManager = LocalBroadcastManager.getInstance(context);
}
if ("bluetooth".equalsIgnoreCase(url.getProtocol())) {
String macAddress = url.getHost().replace("-", ":");
downloader = new BluetoothDownloader(macAddress, url, destFile);
Uri uri = Uri.parse(urlString);
String scheme = uri.getScheme();
if ("bluetooth".equals(scheme)) {
downloader = new BluetoothDownloader(uri, destFile);
} else {
final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD};
Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection);
Repo repo = RepoProvider.Helper.findByUrl(context, uri, projection);
if (repo == null) {
downloader = new HttpDownloader(url, destFile);
downloader = new HttpDownloader(uri, destFile);
} else {
downloader = new HttpDownloader(url, destFile, repo.username, repo.password);
downloader = new HttpDownloader(uri, destFile, repo.username, repo.password);
}
}
return downloader;

View File

@ -31,7 +31,6 @@ import android.os.PatternMatcher;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
@ -42,7 +41,6 @@ import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
/**
* DownloaderService is a service that handles asynchronous download requests
@ -199,7 +197,7 @@ public class DownloaderService extends Service {
downloader = DownloaderFactory.create(this, uri, localFile);
downloader.setListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, long bytesRead, long totalBytes) {
public void onProgress(String urlString, long bytesRead, long totalBytes) {
Intent intent = new Intent(Downloader.ACTION_PROGRESS);
intent.setData(uri);
intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
@ -321,7 +319,7 @@ public class DownloaderService extends Service {
* Check if a URL is actively being downloaded.
*/
private static boolean isActive(String urlString) {
return downloader != null && TextUtils.equals(urlString, downloader.sourceUrl.toString());
return downloader != null && TextUtils.equals(urlString, downloader.urlString);
}
public static void setTimeout(int ms) {

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.net;
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
@ -29,29 +30,30 @@ public class HttpDownloader extends Downloader {
private final String username;
private final String password;
private URL sourceUrl;
private HttpURLConnection connection;
private boolean newFileAvailableOnServer;
HttpDownloader(URL url, File destFile)
HttpDownloader(Uri uri, File destFile)
throws FileNotFoundException, MalformedURLException {
this(url, destFile, null, null);
this(uri, destFile, null, null);
}
/**
* Create a downloader that can authenticate via HTTP Basic Auth using the supplied
* {@code username} and {@code password}.
*
* @param url The file to download
* @param uri The file to download
* @param destFile Where the download is saved
* @param username Username for HTTP Basic Auth, use {@code null} to ignore
* @param password Password for HTTP Basic Auth, use {@code null} to ignore
* @throws FileNotFoundException
* @throws MalformedURLException
*/
HttpDownloader(URL url, File destFile, String username, String password)
HttpDownloader(Uri uri, File destFile, String username, String password)
throws FileNotFoundException, MalformedURLException {
super(url, destFile);
super(uri, destFile);
this.sourceUrl = new URL(urlString);
this.username = username;
this.password = password;
}
@ -95,7 +97,7 @@ public class HttpDownloader extends Downloader {
case 200:
contentLength = tmpConn.getContentLength();
if (!TextUtils.isEmpty(etag) && etag.equals(cacheTag)) {
Utils.debugLog(TAG, sourceUrl + " is cached, not downloading");
Utils.debugLog(TAG, urlString + " is cached, not downloading");
return;
}
newFileAvailableOnServer = true;
@ -104,7 +106,7 @@ public class HttpDownloader extends Downloader {
notFound = true;
return;
default:
Utils.debugLog(TAG, "HEAD check of " + sourceUrl + " returned " + statusCode + ": "
Utils.debugLog(TAG, "HEAD check of " + urlString + " returned " + statusCode + ": "
+ tmpConn.getResponseMessage());
}
@ -118,7 +120,7 @@ public class HttpDownloader extends Downloader {
resumable = true;
}
setupConnection(resumable);
Utils.debugLog(TAG, "downloading " + sourceUrl + " (is resumable: " + resumable + ")");
Utils.debugLog(TAG, "downloading " + urlString + " (is resumable: " + resumable + ")");
downloadFromStream(8192, resumable);
cacheTag = connection.getHeaderField(HEADER_FIELD_ETAG);
}

View File

@ -1,18 +0,0 @@
package sun.net.www.protocol.bluetooth;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* This class is added so that the bluetooth:// scheme we use for the {@link
* org.fdroid.fdroid.net.BluetoothDownloader} is not treated as invalid by
* the {@link URL} class.
*/
public class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
throw new UnsupportedOperationException("openConnection() not supported on bluetooth:// URLs");
}
}