diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java index 93e721e2d..caa4f8c39 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -27,7 +28,10 @@ public class RepoProvider extends FDroidProvider { private Helper() { } - public static Repo findByUri(Context context, Uri uri) { + /** + * Find by the content URI of a repo ({@link RepoProvider#getContentUri(long)}). + */ + public static Repo get(Context context, Uri uri) { ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(uri, Cols.ALL, null, null, null); return cursorToRepo(cursor); @@ -45,6 +49,44 @@ public class RepoProvider extends FDroidProvider { return cursorToRepo(cursor); } + /** + * This method decides what repo a URL belongs to by iteratively removing path fragments and + * checking if it belongs to a repo or not. It will match the most specific repository which + * could serve the file at the given URL. + * + * For any given HTTP resource requested by F-Droid, it should belong to a repository. + * Whether that resource is an index.jar, an icon, or a .apk file, they all belong to a + * repository. Therefore, that repository must exist in the database. The way to find out + * which repository a particular URL came from requires some consideration: + * * Repositories can exist at particular paths on a server (e.g. /fdroid/repo) + * * Individual files can exist at a more specific path on the repo (e.g. /fdroid/repo/icons/org.fdroid.fdroid.png) + * + * So for a given URL "/fdroid/repo/icons/org.fdroid.fdroid.png" we don't actually know + * whether it is for the file "org.fdroid.fdroid.png" at repository "/fdroid/repo/icons" or + * the file "icons/org.fdroid.fdroid.png" at the repository at "/fdroid/repo". + */ + @Nullable + public static Repo findByUrl(Context context, Uri uri, String[] projection) { + Uri withoutQuery = uri.buildUpon().query(null).build(); + Repo repo = findByAddress(context, withoutQuery.toString(), projection); + + // Take a copy of this, because the result of getPathSegments() is an AbstractList + // which doesn't support the remove() operation. + List pathSegments = new ArrayList<>(withoutQuery.getPathSegments()); + + boolean haveTriedWithoutPath = false; + while (repo == null && !haveTriedWithoutPath) { + if (pathSegments.size() == 0) { + haveTriedWithoutPath = true; + } else { + pathSegments.remove(pathSegments.size() - 1); + withoutQuery = withoutQuery.buildUpon().path(TextUtils.join("/", pathSegments)).build(); + } + repo = findByAddress(context, withoutQuery.toString(), projection); + } + return repo; + } + public static Repo findByAddress(Context context, String address) { return findByAddress(context, address, Cols.ALL); } diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java index f89ea3479..19f8d9161 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -280,7 +280,7 @@ public class SwapService extends Service { values.put(Schema.RepoTable.Cols.IN_USE, true); values.put(Schema.RepoTable.Cols.IS_SWAP, true); Uri uri = RepoProvider.Helper.insert(this, values); - repo = RepoProvider.Helper.findByUri(this, uri); + repo = RepoProvider.Helper.get(this, uri); } return repo; diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java index 65e35e6b2..9c908e7a7 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java @@ -4,7 +4,6 @@ import android.content.Context; import android.net.Uri; import android.support.v4.content.LocalBroadcastManager; -import org.apache.commons.io.FilenameUtils; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; @@ -37,7 +36,7 @@ public class DownloaderFactory { public static Downloader create(Context context, String urlString, File destFile) throws IOException { URL url = new URL(urlString); - Downloader downloader = null; + Downloader downloader; if (localBroadcastManager == null) { localBroadcastManager = LocalBroadcastManager.getInstance(context); } @@ -49,8 +48,7 @@ public class DownloaderFactory { downloader = new LocalFileDownloader(url, destFile); } else { final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD}; - String repoUrlString = FilenameUtils.getBaseName(url.toString()); - Repo repo = RepoProvider.Helper.findByAddress(context, repoUrlString, projection); + Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection); if (repo == null) { downloader = new HttpDownloader(url, destFile); } else { diff --git a/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java index 114cffca0..88f3409b9 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid.data; import android.app.Application; import android.content.ContentValues; +import android.net.Uri; import android.support.annotation.Nullable; import org.fdroid.fdroid.BuildConfig; @@ -16,11 +17,61 @@ import org.robolectric.annotation.Config; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; @Config(constants = BuildConfig.class, application = Application.class) @RunWith(RobolectricGradleTestRunner.class) public class RepoProviderTest extends FDroidProviderTest { + private static final String[] COLS = RepoTable.Cols.ALL; + + @Test + public void findByUrl() { + + Repo fdroidRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/repo"); + Repo fdroidArchiveRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/archive"); + + String[] noRepos = { + "https://not-a-repo.example.com", + "https://f-droid.org", + "https://f-droid.org/", + }; + + for (String url : noRepos) { + assertNull(RepoProvider.Helper.findByUrl(context, Uri.parse(url), COLS)); + } + + String[] fdroidRepoUrls = { + "https://f-droid.org/repo/index.jar", + "https://f-droid.org/repo/index.jar?random-junk-in-query=yes", + "https://f-droid.org/repo/index.jar?random-junk-in-query=yes&more-junk", + "https://f-droid.org/repo/icons/org.fdroid.fdroid.100.png", + "https://f-droid.org/repo/icons-640/org.fdroid.fdroid.100.png", + }; + + assertUrlsBelongToRepo(fdroidRepoUrls, fdroidRepo); + + String[] fdroidArchiveUrls = { + "https://f-droid.org/archive/index.jar", + "https://f-droid.org/archive/index.jar?random-junk-in-query=yes", + "https://f-droid.org/archive/index.jar?random-junk-in-query=yes&more-junk", + "https://f-droid.org/archive/icons/org.fdroid.fdroid.100.png", + "https://f-droid.org/archive/icons-640/org.fdroid.fdroid.100.png", + }; + + assertUrlsBelongToRepo(fdroidArchiveUrls, fdroidArchiveRepo); + } + + private void assertUrlsBelongToRepo(String[] urls, Repo expectedRepo) { + for (String url : urls) { + Repo actualRepo = RepoProvider.Helper.findByUrl(context, Uri.parse(url), COLS); + assertNotNull("No repo matching URL " + url, actualRepo); + assertEquals("Invalid repo for URL [" + url + "]. Expected [" + expectedRepo.address + "] but got [" + actualRepo.address + "]", expectedRepo.id, actualRepo.id); + } + + } + /** * The {@link DBHelper} class populates four default repos when it first creates a database: * * F-Droid