summaryrefslogtreecommitdiff
path: root/net/common
diff options
context:
space:
mode:
Diffstat (limited to 'net/common')
-rw-r--r--net/common/README.md3
-rw-r--r--net/common/build.gradle12
-rw-r--r--net/common/src/main/AndroidManifest.xml1
-rw-r--r--net/common/src/main/java/de/danoeh/antennapod/net/common/UrlChecker.java139
-rw-r--r--net/common/src/test/java/de/danoeh/antennapod/net/common/UrlCheckerTest.java175
5 files changed, 330 insertions, 0 deletions
diff --git a/net/common/README.md b/net/common/README.md
new file mode 100644
index 000000000..3bd8b232e
--- /dev/null
+++ b/net/common/README.md
@@ -0,0 +1,3 @@
+# :net:common
+
+This module contains general network related utilities.
diff --git a/net/common/build.gradle b/net/common/build.gradle
new file mode 100644
index 000000000..c519aa653
--- /dev/null
+++ b/net/common/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+ id("com.android.library")
+}
+apply from: "../../common.gradle"
+
+dependencies {
+ annotationProcessor "androidx.annotation:annotation:$annotationVersion"
+ implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
+
+ testImplementation "junit:junit:$junitVersion"
+ testImplementation "org.robolectric:robolectric:$robolectricVersion"
+}
diff --git a/net/common/src/main/AndroidManifest.xml b/net/common/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..702944daa
--- /dev/null
+++ b/net/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="de.danoeh.antennapod.net.common" />
diff --git a/net/common/src/main/java/de/danoeh/antennapod/net/common/UrlChecker.java b/net/common/src/main/java/de/danoeh/antennapod/net/common/UrlChecker.java
new file mode 100644
index 000000000..4eb1fd6a5
--- /dev/null
+++ b/net/common/src/main/java/de/danoeh/antennapod/net/common/UrlChecker.java
@@ -0,0 +1,139 @@
+package de.danoeh.antennapod.net.common;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import android.util.Log;
+
+import okhttp3.HttpUrl;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Provides methods for checking and editing a URL.
+ */
+public final class UrlChecker {
+
+ /**
+ * Class shall not be instantiated.
+ */
+ private UrlChecker() {
+ }
+
+ /**
+ * Logging tag.
+ */
+ private static final String TAG = "UrlChecker";
+
+ private static final String AP_SUBSCRIBE = "antennapod-subscribe://";
+ private static final String AP_SUBSCRIBE_DEEPLINK = "antennapod.org/deeplink/subscribe";
+
+ /**
+ * Checks if URL is valid and modifies it if necessary.
+ *
+ * @param url The url which is going to be prepared
+ * @return The prepared url
+ */
+ public static String prepareUrl(@NonNull String url) {
+ url = url.trim();
+ String lowerCaseUrl = url.toLowerCase(Locale.ROOT); // protocol names are case insensitive
+ if (lowerCaseUrl.startsWith("feed://")) {
+ Log.d(TAG, "Replacing feed:// with http://");
+ return prepareUrl(url.substring("feed://".length()));
+ } else if (lowerCaseUrl.startsWith("pcast://")) {
+ Log.d(TAG, "Removing pcast://");
+ return prepareUrl(url.substring("pcast://".length()));
+ } else if (lowerCaseUrl.startsWith("pcast:")) {
+ Log.d(TAG, "Removing pcast:");
+ return prepareUrl(url.substring("pcast:".length()));
+ } else if (lowerCaseUrl.startsWith("itpc")) {
+ Log.d(TAG, "Replacing itpc:// with http://");
+ return prepareUrl(url.substring("itpc://".length()));
+ } else if (lowerCaseUrl.startsWith(AP_SUBSCRIBE)) {
+ Log.d(TAG, "Removing antennapod-subscribe://");
+ return prepareUrl(url.substring(AP_SUBSCRIBE.length()));
+ } else if (lowerCaseUrl.contains(AP_SUBSCRIBE_DEEPLINK)) {
+ Log.d(TAG, "Removing " + AP_SUBSCRIBE_DEEPLINK);
+ String removedWebsite = url.substring(url.indexOf("?url=") + "?url=".length());
+ try {
+ return prepareUrl(URLDecoder.decode(removedWebsite, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ return prepareUrl(removedWebsite);
+ }
+ } else if (!(lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://"))) {
+ Log.d(TAG, "Adding http:// at the beginning of the URL");
+ return "http://" + url;
+ } else {
+ return url;
+ }
+ }
+
+ /**
+ * Checks if URL is valid and modifies it if necessary.
+ * This method also handles protocol relative URLs.
+ *
+ * @param url The url which is going to be prepared
+ * @param base The url against which the (possibly relative) url is applied. If this is null,
+ * the result of prepareURL(url) is returned instead.
+ * @return The prepared url
+ */
+ public static String prepareUrl(String url, String base) {
+ if (base == null) {
+ return prepareUrl(url);
+ }
+ url = url.trim();
+ base = prepareUrl(base);
+ Uri urlUri = Uri.parse(url);
+ Uri baseUri = Uri.parse(base);
+ if (urlUri.isRelative() && baseUri.isAbsolute()) {
+ return urlUri.buildUpon().scheme(baseUri.getScheme()).build().toString();
+ } else {
+ return prepareUrl(url);
+ }
+ }
+
+ public static boolean containsUrl(List<String> list, String url) {
+ for (String item : list) {
+ if (urlEquals(item, url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean urlEquals(String string1, String string2) {
+ HttpUrl url1 = HttpUrl.parse(string1);
+ HttpUrl url2 = HttpUrl.parse(string2);
+ if (!url1.host().equals(url2.host())) {
+ return false;
+ }
+ List<String> pathSegments1 = normalizePathSegments(url1.pathSegments());
+ List<String> pathSegments2 = normalizePathSegments(url2.pathSegments());
+ if (!pathSegments1.equals(pathSegments2)) {
+ return false;
+ }
+ if (TextUtils.isEmpty(url1.query())) {
+ return TextUtils.isEmpty(url2.query());
+ }
+ return url1.query().equals(url2.query());
+ }
+
+ /**
+ * Removes empty segments and converts all to lower case.
+ * @param input List of path segments
+ * @return Normalized list of path segments
+ */
+ private static List<String> normalizePathSegments(List<String> input) {
+ List<String> result = new ArrayList<>();
+ for (String string : input) {
+ if (!TextUtils.isEmpty(string)) {
+ result.add(string.toLowerCase(Locale.ROOT));
+ }
+ }
+ return result;
+ }
+}
diff --git a/net/common/src/test/java/de/danoeh/antennapod/net/common/UrlCheckerTest.java b/net/common/src/test/java/de/danoeh/antennapod/net/common/UrlCheckerTest.java
new file mode 100644
index 000000000..ba9f1dcbb
--- /dev/null
+++ b/net/common/src/test/java/de/danoeh/antennapod/net/common/UrlCheckerTest.java
@@ -0,0 +1,175 @@
+package de.danoeh.antennapod.net.common;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link UrlChecker}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class UrlCheckerTest {
+
+ @Test
+ public void testCorrectURLHttp() {
+ final String in = "http://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals(in, out);
+ }
+
+ @Test
+ public void testCorrectURLHttps() {
+ final String in = "https://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals(in, out);
+ }
+
+ @Test
+ public void testMissingProtocol() {
+ final String in = "example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testFeedProtocol() {
+ final String in = "feed://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testPcastProtocolNoScheme() {
+ final String in = "pcast://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testItpcProtocol() {
+ final String in = "itpc://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testItpcProtocolWithScheme() {
+ final String in = "itpc://https://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("https://example.com", out);
+ }
+
+ @Test
+ public void testWhiteSpaceUrlShouldNotAppend() {
+ final String in = "\n http://example.com \t";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testWhiteSpaceShouldAppend() {
+ final String in = "\n example.com \t";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testAntennaPodSubscribeProtocolNoScheme() {
+ final String in = "antennapod-subscribe://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testPcastProtocolWithScheme() {
+ final String in = "pcast://https://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("https://example.com", out);
+ }
+
+ @Test
+ public void testAntennaPodSubscribeProtocolWithScheme() {
+ final String in = "antennapod-subscribe://https://example.com";
+ final String out = UrlChecker.prepareUrl(in);
+ assertEquals("https://example.com", out);
+ }
+
+ @Test
+ public void testAntennaPodSubscribeDeeplink() throws UnsupportedEncodingException {
+ final String feed = "http://example.org/podcast.rss";
+ assertEquals(feed, UrlChecker.prepareUrl("https://antennapod.org/deeplink/subscribe?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("http://antennapod.org/deeplink/subscribe?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("http://antennapod.org/deeplink/subscribe/?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("https://www.antennapod.org/deeplink/subscribe?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("http://www.antennapod.org/deeplink/subscribe?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("http://www.antennapod.org/deeplink/subscribe/?url=" + feed));
+ assertEquals(feed, UrlChecker.prepareUrl("http://www.antennapod.org/deeplink/subscribe?url="
+ + URLEncoder.encode(feed, "UTF-8")));
+ assertEquals(feed, UrlChecker.prepareUrl("http://www.antennapod.org/deeplink/subscribe?url="
+ + "example.org/podcast.rss"));
+ }
+
+ @Test
+ public void testProtocolRelativeUrlIsAbsolute() {
+ final String in = "https://example.com";
+ final String inBase = "http://examplebase.com";
+ final String out = UrlChecker.prepareUrl(in, inBase);
+ assertEquals(in, out);
+ }
+
+ @Test
+ public void testProtocolRelativeUrlIsRelativeHttps() {
+ final String in = "//example.com";
+ final String inBase = "https://examplebase.com";
+ final String out = UrlChecker.prepareUrl(in, inBase);
+ assertEquals("https://example.com", out);
+ }
+
+ @Test
+ public void testProtocolRelativeUrlIsHttpsWithApSubscribeProtocol() {
+ final String in = "//example.com";
+ final String inBase = "antennapod-subscribe://https://examplebase.com";
+ final String out = UrlChecker.prepareUrl(in, inBase);
+ assertEquals("https://example.com", out);
+ }
+
+ @Test
+ public void testProtocolRelativeUrlBaseUrlNull() {
+ final String in = "example.com";
+ final String out = UrlChecker.prepareUrl(in, null);
+ assertEquals("http://example.com", out);
+ }
+
+ @Test
+ public void testUrlEqualsSame() {
+ assertTrue(UrlChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test"));
+ assertTrue(UrlChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test/"));
+ assertTrue(UrlChecker.urlEquals("https://www.example.com/test", "https://www.example.com//test"));
+ assertTrue(UrlChecker.urlEquals("https://www.example.com", "https://www.example.com/"));
+ assertTrue(UrlChecker.urlEquals("https://www.example.com", "http://www.example.com"));
+ assertTrue(UrlChecker.urlEquals("http://www.example.com/", "https://www.example.com/"));
+ assertTrue(UrlChecker.urlEquals("https://www.example.com/?id=42", "https://www.example.com/?id=42"));
+ assertTrue(UrlChecker.urlEquals("https://example.com/podcast%20test", "https://example.com/podcast test"));
+ assertTrue(UrlChecker.urlEquals("https://example.com/?a=podcast%20test", "https://example.com/?a=podcast test"));
+ assertTrue(UrlChecker.urlEquals("https://example.com/?", "https://example.com/"));
+ assertTrue(UrlChecker.urlEquals("https://example.com/?", "https://example.com"));
+ assertTrue(UrlChecker.urlEquals("https://Example.com", "https://example.com"));
+ assertTrue(UrlChecker.urlEquals("https://example.com/test", "https://example.com/Test"));
+ }
+
+ @Test
+ public void testUrlEqualsDifferent() {
+ assertFalse(UrlChecker.urlEquals("https://www.example.com/test", "https://www.example2.com/test"));
+ assertFalse(UrlChecker.urlEquals("https://www.example.com/test", "https://www.example.de/test"));
+ assertFalse(UrlChecker.urlEquals("https://example.com/", "https://otherpodcast.example.com/"));
+ assertFalse(UrlChecker.urlEquals("https://www.example.com/?id=42&a=b", "https://www.example.com/?id=43&a=b"));
+ assertFalse(UrlChecker.urlEquals("https://example.com/podcast%25test", "https://example.com/podcast test"));
+ }
+}