Follow HTTP redirects when loading internet radio playlists

master
François-Xavier Thomas 6 years ago
parent 9ff5536d9b
commit 53b174a8f4
  1. 120
      airsonic-main/src/main/java/org/airsonic/player/service/InternetRadioService.java

@ -10,8 +10,8 @@ import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.util.*; import java.util.*;
@Service @Service
@ -29,28 +29,46 @@ public class InternetRadioService {
*/ */
private static final long PLAYLIST_REMOTE_MAX_BYTE_SIZE = 100 * 1024; // 100 kB private static final long PLAYLIST_REMOTE_MAX_BYTE_SIZE = 100 * 1024; // 100 kB
/**
* The maximum number of redirects for a remote playlist response.
*/
private static final int PLAYLIST_REMOTE_MAX_REDIRECTS = 20;
/** /**
* A list of cached source URLs for remote playlists. * A list of cached source URLs for remote playlists.
*/ */
private Map<Integer, List<InternetRadioSource>> cachedSources; private Map<Integer, List<InternetRadioSource>> cachedSources;
/**
* Generic exception class for playlists.
*/
private class PlaylistException extends Exception {
public PlaylistException(String message) { super(message); }
}
/** /**
* Exception thrown when the remote playlist is too large to be parsed completely. * Exception thrown when the remote playlist is too large to be parsed completely.
*/ */
private class PlaylistTooLarge extends Exception { private class PlaylistTooLarge extends PlaylistException {
public PlaylistTooLarge(String message) { public PlaylistTooLarge(String message) { super(message); }
super(message);
}
} }
/** /**
* Exception thrown when the remote playlist format cannot be determined. * Exception thrown when the remote playlist format cannot be determined.
*/ */
private class PlaylistFormatUnsupported extends Exception { private class PlaylistFormatUnsupported extends PlaylistException {
public PlaylistFormatUnsupported(String message) { public PlaylistFormatUnsupported(String message) {
super(message); super(message);
} }
} }
/**
* Exception thrown when too many redirects occurred when retrieving a remote playlist.
*/
private class PlaylistHasTooManyRedirects extends PlaylistException {
public PlaylistHasTooManyRedirects(String message) { super(message); }
}
public InternetRadioService() { public InternetRadioService() {
this.cachedSources = new HashMap<>(); this.cachedSources = new HashMap<>();
} }
@ -114,7 +132,12 @@ public class InternetRadioService {
* @throws Exception * @throws Exception
*/ */
private List<InternetRadioSource> retrieveInternetRadioSources(InternetRadio radio) throws Exception { private List<InternetRadioSource> retrieveInternetRadioSources(InternetRadio radio) throws Exception {
return retrieveInternetRadioSources(radio, PLAYLIST_REMOTE_MAX_LENGTH, PLAYLIST_REMOTE_MAX_BYTE_SIZE); return retrieveInternetRadioSources(
radio,
PLAYLIST_REMOTE_MAX_LENGTH,
PLAYLIST_REMOTE_MAX_BYTE_SIZE,
PLAYLIST_REMOTE_MAX_REDIRECTS
);
} }
/** /**
@ -122,14 +145,16 @@ public class InternetRadioService {
* *
* @param radio an internet radio * @param radio an internet radio
* @param maxCount the maximum number of items to read from the remote playlist, or 0 if unlimited * @param maxCount the maximum number of items to read from the remote playlist, or 0 if unlimited
* @param maxByteSize maximum size of the response, in bytes, or 0 if unlimited
* @param maxRedirects maximum number of redirects, or 0 if unlimited
* @return a list of internet radio sources * @return a list of internet radio sources
* @throws Exception * @throws Exception
*/ */
private List<InternetRadioSource> retrieveInternetRadioSources(InternetRadio radio, int maxCount, long maxByteSize) throws Exception { private List<InternetRadioSource> retrieveInternetRadioSources(InternetRadio radio, int maxCount, long maxByteSize, int maxRedirects) throws Exception {
// Retrieve the remote playlist // Retrieve the remote playlist
String playlistUrl = radio.getStreamUrl(); String playlistUrl = radio.getStreamUrl();
LOG.debug("Parsing internet radio playlist at {}...", playlistUrl); LOG.debug("Parsing internet radio playlist at {}...", playlistUrl);
SpecificPlaylist inputPlaylist = retrievePlaylist(new URL(playlistUrl), maxByteSize); SpecificPlaylist inputPlaylist = retrievePlaylist(new URL(playlistUrl), maxByteSize, maxRedirects);
// Retrieve stream URLs // Retrieve stream URLs
List<InternetRadioSource> entries = new ArrayList<>(); List<InternetRadioSource> entries = new ArrayList<>();
@ -194,33 +219,84 @@ public class InternetRadioService {
/** /**
* Retrieve playlist data from a given URL. * Retrieve playlist data from a given URL.
* *
* This throws an ec
*
* @param url URL to the remote playlist * @param url URL to the remote playlist
* @param maxByteSize maximum size of the response, in bytes, or 0 if unlimited * @param maxByteSize maximum size of the response, in bytes, or 0 if unlimited
* @param maxRedirects maximum number of redirects, or 0 if unlimited
* @return the remote playlist data * @return the remote playlist data
*/ */
private SpecificPlaylist retrievePlaylist(URL url, long maxByteSize) throws IOException, PlaylistFormatUnsupported { private SpecificPlaylist retrievePlaylist(URL url, long maxByteSize, int maxRedirects) throws IOException, PlaylistException {
URLConnection urlConnection = url.openConnection();
urlConnection.setAllowUserInteraction(false); SpecificPlaylist playlist;
urlConnection.setConnectTimeout(10000); HttpURLConnection urlConnection = connectToURLWithRedirects(url, maxRedirects);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(false);
urlConnection.setReadTimeout(60000);
urlConnection.setUseCaches(true);
urlConnection.connect();
String contentEncoding = urlConnection.getContentEncoding();
SpecificPlaylist playlist = null;
try (InputStream in = urlConnection.getInputStream()) { try (InputStream in = urlConnection.getInputStream()) {
String contentEncoding = urlConnection.getContentEncoding();
if (maxByteSize > 0) { if (maxByteSize > 0) {
playlist = SpecificPlaylistFactory.getInstance().readFrom(new BoundedInputStream(in, maxByteSize), contentEncoding); playlist = SpecificPlaylistFactory.getInstance().readFrom(new BoundedInputStream(in, maxByteSize), contentEncoding);
} else { } else {
playlist = SpecificPlaylistFactory.getInstance().readFrom(in, contentEncoding); playlist = SpecificPlaylistFactory.getInstance().readFrom(in, contentEncoding);
} }
} }
finally {
urlConnection.disconnect();
}
if (playlist == null) { if (playlist == null) {
throw new PlaylistFormatUnsupported("Unsupported playlist format " + url.toString()); throw new PlaylistFormatUnsupported("Unsupported playlist format " + url.toString());
} }
return playlist; return playlist;
} }
/**
* Start a new connection to a remote URL, and follow redirects.
*
* @param url the remote URL
* @param maxRedirects maximum number of redirects, or 0 if unlimited
* @return an open connection
*/
private HttpURLConnection connectToURLWithRedirects(URL url, int maxRedirects) throws IOException, PlaylistException {
int redirectCount = 0;
URL currentURL = url;
// Start a first connection.
HttpURLConnection connection = connectToURL(currentURL);
// While it redirects, follow redirects in new connections.
while (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM ||
connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP ||
connection.getResponseCode() == HttpURLConnection.HTTP_SEE_OTHER) {
// Check if redirect count is not too large.
redirectCount += 1;
if (maxRedirects > 0 && redirectCount > maxRedirects) {
connection.disconnect();
throw new PlaylistHasTooManyRedirects(String.format("Too many redirects ({0}) for URL {1}", redirectCount, url));
}
// Reconnect to the new URL.
currentURL = new URL(connection.getHeaderField("Location"));
connection.disconnect();
connection = connectToURL(currentURL);
}
// Return the last connection that did not redirect.
return connection;
}
/**
* Start a new connection to a remote URL.
*
* @param url the remote URL
* @return an open connection
*/
private HttpURLConnection connectToURL(URL url) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setAllowUserInteraction(false);
urlConnection.setConnectTimeout(10000);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(false);
urlConnection.setReadTimeout(60000);
urlConnection.setUseCaches(true);
urlConnection.connect();
return urlConnection;
}
} }

Loading…
Cancel
Save