diff --git a/src/de/danoeh/antennapod/util/syndication/FeedDiscoverer.java b/src/de/danoeh/antennapod/util/syndication/FeedDiscoverer.java
new file mode 100644
index 000000000..ac38ec876
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/syndication/FeedDiscoverer.java
@@ -0,0 +1,78 @@
+package de.danoeh.antennapod.util.syndication;
+
+import android.net.Uri;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Finds RSS/Atom URLs in a HTML document using the auto-discovery techniques described here:
+ *
+ * http://www.rssboard.org/rss-autodiscovery
+ *
+ * http://blog.whatwg.org/feed-autodiscovery
+ */
+public class FeedDiscoverer {
+
+ private static final String MIME_RSS = "application/rss+xml";
+ private static final String MIME_ATOM = "application/atom+xml";
+
+ /**
+ * Discovers links to RSS and Atom feeds in the given File which must be a HTML document.
+ *
+ * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if
+ * a title cannot be found).
+ */
+ public Map findLinks(File in, String baseUrl) throws IOException {
+ return findLinks(Jsoup.parse(in, null), baseUrl);
+ }
+
+ /**
+ * Discovers links to RSS and Atom feeds in the given File which must be a HTML document.
+ *
+ * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if
+ * a title cannot be found).
+ */
+ public Map findLinks(String in, String baseUrl) throws IOException {
+ return findLinks(Jsoup.parse(in), baseUrl);
+ }
+
+ private Map findLinks(Document document, String baseUrl) {
+ Map res = new LinkedHashMap();
+ Elements links = document.head().getElementsByTag("link");
+ for (Element link : links) {
+ String rel = link.attr("rel");
+ String href = link.attr("href");
+ if (!StringUtils.isEmpty(href) &&
+ (rel.equals("alternate") || rel.equals("feed"))) {
+ String type = link.attr("type");
+ if (type.equals(MIME_RSS) || type.equals(MIME_ATOM)) {
+ String title = link.attr("title");
+ String processedUrl = processURL(baseUrl, href);
+ if (processedUrl != null) {
+ res.put(processedUrl,
+ (StringUtils.isEmpty(title)) ? href : title);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ private String processURL(String baseUrl, String strUrl) {
+ Uri uri = Uri.parse(strUrl);
+ if (uri.isRelative()) {
+ Uri res = Uri.parse(baseUrl).buildUpon().path(strUrl).build();
+ return (res != null) ? res.toString() : null;
+ } else {
+ return strUrl;
+ }
+ }
+}
diff --git a/src/instrumentationTest/de/test/antennapod/util/syndication/FeedDiscovererTest.java b/src/instrumentationTest/de/test/antennapod/util/syndication/FeedDiscovererTest.java
new file mode 100644
index 000000000..51a28089d
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/util/syndication/FeedDiscovererTest.java
@@ -0,0 +1,109 @@
+package instrumentationTest.de.test.antennapod.util.syndication;
+
+import android.test.InstrumentationTestCase;
+import de.danoeh.antennapod.util.syndication.FeedDiscoverer;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Map;
+
+/**
+ * Test class for FeedDiscoverer
+ */
+public class FeedDiscovererTest extends InstrumentationTestCase {
+
+ private FeedDiscoverer fd;
+
+ private File testDir;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ fd = new FeedDiscoverer();
+ testDir = getInstrumentation().getTargetContext().getExternalFilesDir("FeedDiscovererTest");
+ testDir.mkdir();
+ assertTrue(testDir.exists());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ FileUtils.deleteDirectory(testDir);
+ super.tearDown();
+ }
+
+ private String createTestHtmlString(String rel, String type, String href, String title) {
+ return String.format("Test",
+ rel, type, href, title);
+ }
+
+ private String createTestHtmlString(String rel, String type, String href) {
+ return String.format("Test",
+ rel, type, href);
+ }
+
+ private void checkFindUrls(boolean isAlternate, boolean isRss, boolean withTitle, boolean isAbsolute, boolean fromString) throws Exception {
+ final String title = "Test title";
+ final String hrefAbs = "http://example.com/feed";
+ final String hrefRel = "/feed";
+ final String base = "http://example.com";
+
+ final String rel = (isAlternate) ? "alternate" : "feed";
+ final String type = (isRss) ? "application/rss+xml" : "application/atom+xml";
+ final String href = (isAbsolute) ? hrefAbs : hrefRel;
+
+ Map res;
+ String html = (withTitle) ? createTestHtmlString(rel, type, href, title)
+ : createTestHtmlString(rel, type, href);
+ if (fromString) {
+ res = fd.findLinks(html, base);
+ } else {
+ File testFile = new File(testDir, "feed");
+ FileOutputStream out = new FileOutputStream(testFile);
+ IOUtils.write(html, out);
+ out.close();
+ res = fd.findLinks(testFile, base);
+ }
+
+ assertNotNull(res);
+ assertEquals(1, res.size());
+ for (String key : res.keySet()) {
+ assertEquals(hrefAbs, key);
+ }
+ assertTrue(res.containsKey(hrefAbs));
+ if (withTitle) {
+ assertEquals(title, res.get(hrefAbs));
+ } else {
+ assertEquals(href, res.get(hrefAbs));
+ }
+ }
+
+ public void testAlternateRSSWithTitleAbsolute() throws Exception {
+ checkFindUrls(true, true, true, true, true);
+ }
+
+ public void testAlternateRSSWithTitleRelative() throws Exception {
+ checkFindUrls(true, true, true, false, true);
+ }
+
+ public void testAlternateRSSNoTitleAbsolute() throws Exception {
+ checkFindUrls(true, true, false, true, true);
+ }
+
+ public void testAlternateRSSNoTitleRelative() throws Exception {
+ checkFindUrls(true, true, false, false, true);
+ }
+
+ public void testAlternateAtomWithTitleAbsolute() throws Exception {
+ checkFindUrls(true, false, true, true, true);
+ }
+
+ public void testFeedAtomWithTitleAbsolute() throws Exception {
+ checkFindUrls(false, false, true, true, true);
+ }
+
+ public void testAlternateRSSWithTitleAbsoluteFromFile() throws Exception {
+ checkFindUrls(true, true, true, true, false);
+ }
+}