diff --git a/src/freenet/client/filter/HTMLFilter.java b/src/freenet/client/filter/HTMLFilter.java
index 95b716ffdf5..f4a643d6d19 100644
--- a/src/freenet/client/filter/HTMLFilter.java
+++ b/src/freenet/client/filter/HTMLFilter.java
@@ -43,6 +43,9 @@
public class HTMLFilter implements ContentDataFilter, CharsetExtractor {
+ private static final String M3U_PLAYER_TAG_FILE = "freenet/clients/http/staticfiles/js/m3u-player.js";
+ /** if true, embed m3u player. Enabled when fproxy javascript is enabled. **/
+ public static boolean embedM3uPlayer = true;
private static boolean logMINOR;
private static boolean logDEBUG;
@@ -61,6 +64,8 @@ public class HTMLFilter implements ContentDataFilter, CharsetExtractor {
/** -1 means don't allow it */
public static int metaRefreshRedirectMinInterval = 30;
+ private static final String m3uPlayerScriptTagContent = m3uPlayerScriptTagContent();
+
@Override
public void readFilter(
InputStream input, OutputStream output, String charset, Map otherParams,
@@ -559,6 +564,30 @@ else if((c < 32) && (c != '\t') && (c != '\n') && (c != '\r')) {
w.write(sout);
}
+ static String m3uPlayerScriptTagContent() {
+ InputStream m3uPlayerTagStream = HTMLFilter.class.getClassLoader()
+ .getResourceAsStream(M3U_PLAYER_TAG_FILE);
+ String errorTag = "/* Error: could not load " + M3U_PLAYER_TAG_FILE + " */";
+ if (m3uPlayerTagStream == null) {
+ return errorTag;
+ }
+ String tagContent;
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(m3uPlayerTagStream))) {
+ StringBuilder stringBuilder = new StringBuilder("");
+ tagContent = stringBuilder.toString();
+ } catch (IOException e) {
+ Logger.error(HTMLFilter.class, "Could not read m3uPlayer inline-script.");
+ return errorTag;
+ }
+ return tagContent;
+ }
+
String processTag(List splitTag, Writer w, HTMLParseContext pc)
throws IOException, DataFilterException {
// First, check that it is a recognized tag
@@ -578,6 +607,9 @@ String processTag(List splitTag, Writer w, HTMLParseContext pc)
if(t.element.compareTo("head")==0 && !t.startSlash){
pc.wasHeadElementFound=true;
} else if(t.element.compareTo("head")==0 && t.startSlash) {
+ if (embedM3uPlayer) {
+ w.write(m3uPlayerScriptTagContent);
+ }
pc.headEnded = true;
if(pc.onlyDetectingCharset) pc.failedDetectCharset = true;
//If we found a or a without a , then we need to add them to a
@@ -592,7 +624,12 @@ String processTag(List splitTag, Writer w, HTMLParseContext pc)
throwFilterException(l10n("metaOutsideHead"));
//If we found a and haven't closed already, then we do
}else if(t.element.compareTo("body") == 0 && pc.openElements.contains("head")){
- if(!pc.onlyDetectingCharset) w.write("");
+ if(!pc.onlyDetectingCharset) {
+ if (embedM3uPlayer) {
+ w.write(m3uPlayerScriptTagContent);
+ }
+ w.write("");
+ }
pc.headEnded = true;
if(pc.onlyDetectingCharset) pc.failedDetectCharset = true;
pc.openElements.pop();
@@ -601,7 +638,12 @@ String processTag(List splitTag, Writer w, HTMLParseContext pc)
pc.wasHeadElementFound=true;
String headContent=pc.cb.processTag(new ParsedTag("head", new HashMap()));
if(headContent!=null){
- if(!pc.onlyDetectingCharset) w.write(headContent+"");
+ if(!pc.onlyDetectingCharset) {
+ if (embedM3uPlayer) {
+ w.write(m3uPlayerScriptTagContent);
+ }
+ w.write(headContent+"");
+ }
pc.headEnded = true;
if(pc.onlyDetectingCharset) pc.failedDetectCharset = true;
}
diff --git a/src/freenet/clients/http/SimpleToadletServer.java b/src/freenet/clients/http/SimpleToadletServer.java
index b39b271086f..ce5e09d7068 100644
--- a/src/freenet/clients/http/SimpleToadletServer.java
+++ b/src/freenet/clients/http/SimpleToadletServer.java
@@ -758,6 +758,21 @@ public void set(Integer val)
}, false);
HTMLFilter.metaRefreshRedirectMinInterval = Math.max(-1, fproxyConfig.getInt("metaRefreshRedirectInterval"));
+ fproxyConfig.register("embedM3uPlayerInFreesites", true, configItemOrder++, true, false, "SimpleToadletServer.embedM3uPlayerInFreesites", "SimpleToadletServer.embedM3uPlayerInFreesitesLong",
+ new BooleanCallback() {
+
+ @Override
+ public Boolean get() {
+ return HTMLFilter.embedM3uPlayer;
+ }
+
+ @Override
+ public void set(Boolean val) {
+ HTMLFilter.embedM3uPlayer = val;
+ }
+ });
+ HTMLFilter.embedM3uPlayer = fproxyConfig.getBoolean("embedM3uPlayerInFreesites");
+
fproxyConfig.register("refilterPolicy", "RE_FILTER",
configItemOrder++, true, false, "SimpleToadletServer.refilterPolicy", "SimpleToadletServer.refilterPolicyLong", new ReFilterCallback());
diff --git a/src/freenet/clients/http/ToadletContextImpl.java b/src/freenet/clients/http/ToadletContextImpl.java
index c6a4f514b5f..f24520686fd 100644
--- a/src/freenet/clients/http/ToadletContextImpl.java
+++ b/src/freenet/clients/http/ToadletContextImpl.java
@@ -18,7 +18,9 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
+import java.util.List;
import java.util.Locale;
+import java.util.StringJoiner;
import java.util.TimeZone;
import freenet.clients.http.FProxyFetchInProgress.REFILTER_POLICY;
@@ -445,9 +447,12 @@ static void sendReplyHeaders(OutputStream sockOutputStream, int replyCode, Strin
private static String generateCSP(boolean allowScripts, boolean allowFrames) {
StringBuilder sb = new StringBuilder();
- sb.append("default-src 'self'; script-src ");
+ // allow access to blobs, because these are purely local
+ sb.append("default-src 'self' blob:; script-src ");
// "options inline-script" is old syntax needed for older Firefox's.
- sb.append(allowScripts ? "'self' 'unsafe-inline'; options inline-script" : "'none'");
+ sb.append(allowScripts
+ ? "'self' 'unsafe-inline'; options inline-script"
+ : generateRestrictedScriptSrc());
sb.append("; frame-src ");
sb.append(allowFrames ? "'self'" : "'none'");
sb.append("; object-src 'none'");
@@ -457,8 +462,24 @@ private static String generateCSP(boolean allowScripts, boolean allowFrames) {
return sb.toString();
}
- static TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
-
+ private static String generateRestrictedScriptSrc() {
+ // TODO: auto-generate these hashes from the path to the source file
+ String[] allowedScriptHashes = new String[] {
+ "sha256-emGXuxNdTQP2ylJOeGhLCKYFO+1g/2u6FtPSzMKQ06A=" // freenet/clients/http/staticfiles/js/m3u-player.js
+ };
+ if (allowedScriptHashes.length == 0) {
+ return "'none'";
+ } else {
+ StringJoiner stringJoiner = new StringJoiner("' '", "'", "'");
+ for (String source : allowedScriptHashes) {
+ stringJoiner.add(source);
+ }
+ return stringJoiner.toString();
+ }
+ }
+
+ static TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
+
public static Date parseHTTPDate(String httpDate) throws java.text.ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",Locale.US);
sdf.setTimeZone(TZ_UTC);
diff --git a/src/freenet/clients/http/staticfiles/js/m3u-player.js b/src/freenet/clients/http/staticfiles/js/m3u-player.js
new file mode 100644
index 00000000000..8ca19dff078
--- /dev/null
+++ b/src/freenet/clients/http/staticfiles/js/m3u-player.js
@@ -0,0 +1,249 @@
+// [[file:m3u-player.org::*The script][The script:1]]
+// @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2-or-Later
+// When changing even a single letter in this file, you MUST adjust
+// freenet.clients.http.ToadletContextImpl.generateRestrictedScriptSrc,
+// otherwise the CSP policy will prevent loading this script.
+// use the following shell-command to get the new hash:
+// sha256sum m3u-player.js | cut -d " " -f 1 | xxd -r -p | base64
+// AVOID unicode characters. They may change on the way to the
+// browser, invalidating the CSP header.
+// Also you MUST escape any ampersand and less-than or greater-than signs,
+// because this file will be inlined into the site.
+const playlists = {};
+const prefetchedTracks = new Map(); // use a map for insertion order, so we can just blow away old entries.
+// maximum prefetched blobs that are kept.
+const MAX_PREFETCH_KEEP = 10;
+// maximum allowed number of entries in a playlist to prevent OOM attacks against the browser with self-referencing playlists
+const MAX_PLAYLIST_LENGTH = 1000;
+const PLAYLIST_MIME_TYPES = ["audio/x-mpegurl", "audio/mpegurl", "application/vnd.apple.mpegurl","application/mpegurl","application/x-mpegurl"];
+function stripUrlParameters(link) {
+ const url = new URL(link, window.location);
+ url.search = "";
+ url.hash = "";
+ return url.href;
+}
+function isPlaylist(link) {
+ const linkHref = stripUrlParameters(link);
+ return linkHref.endsWith(".m3u") || linkHref.endsWith(".m3u8");
+}
+function isBlob(link) {
+ return new URL(link, window.location).protocol == 'blob';
+}
+/**
+ * Replace the host of the link with the host seen by the browser to get around CSP restrictions.
+ * This must be applied to all links read from anywhere.
+ * invalid links will fail at the URL constructor.
+ *
+ * Keep in mind that the link here is user input, though already
+ * filtered by the m3u-filter (we check against the
+ * PLAYLIST_MIME_TYPES whether Freenet saw this as m3u-list),
+ * and all user input is potentially evil.
+ */
+function replaceHost(link) {
+ const url = new URL(link, window.location);
+ if (url.protocol !== 'blob:') {
+ url.host = window.location.host;
+ url.port = window.location.port;
+ }
+ return url.href;
+}
+function parsePlaylist(textContent) {
+ return textContent.match(/^(?!#)(?!\s).*$/mg)
+ .filter(s => s) // filter removes empty strings
+ .map(replaceHost); // now all tracks point to our local installation. The m3u-filter ensures that they are valid keys.
+}
+/**
+ * Download the given playlist, parse it, and store the tracks in the
+ * global playlists object using the url as key.
+ *
+ * Runs callback once the playlist downloaded successfully.
+ */
+function fetchPlaylist(url, onload, onerror) {
+ const playlistFetcher = new XMLHttpRequest();
+ playlistFetcher.open("GET", url, true);
+ playlistFetcher.responseType = "blob"; // to get a mime type
+ playlistFetcher.onload = () => {
+ if (PLAYLIST_MIME_TYPES.includes(playlistFetcher.response.type)) { // security check to ensure that filters have run
+ const reader = new FileReader();
+ const load = onload; // propagate to inner scope
+ reader.addEventListener("loadend", e => {
+ playlists[url] = parsePlaylist(reader.result);
+ onload();
+ });
+ reader.readAsText(playlistFetcher.response);
+ } else {
+ console.error("playlist must have one of the playlist MIME type '" + PLAYLIST_MIME_TYPES + "' but it had MIME type '" + playlistFetcher.response.type + "'.");
+ onerror();
+ }
+ };
+ playlistFetcher.onerror = onerror;
+ playlistFetcher.abort = onerror;
+ playlistFetcher.send();
+}
+function prefetchTrack(url, onload) {
+ if (prefetchedTracks.has(url)) {
+ return;
+ }
+ // first cleanup: kill the oldest entries until we're back at the allowed size
+ while (prefetchedTracks.size > MAX_PREFETCH_KEEP) {
+ const key = prefetchedTracks.keys().next().value;
+ const track = prefetchedTracks.get(key);
+ prefetchedTracks.delete(key);
+ }
+ // first set the prefetched to the url so we will never request twice
+ prefetchedTracks.set(url, url);
+ // now start replacing it with a blob
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "blob";
+ xhr.onload = () => {
+ prefetchedTracks.set(url, xhr.response);
+ if (onload) {
+ onload();
+ }
+ };
+ xhr.send();
+}
+function updateSrc(mediaTag, callback) {
+ const playlistUrl = mediaTag.getAttribute("playlist");
+ const trackIndex = mediaTag.getAttribute("track-index");
+ // deepcopy playlists to avoid shared mutation
+ let playlist = [...playlists[playlistUrl]];
+ let trackUrl = playlist[trackIndex];
+ // strip out the host; we do not need that in Freenet
+ // download and splice in playlists as needed
+ if (isPlaylist(trackUrl)) {
+ if (playlist.length >= MAX_PLAYLIST_LENGTH) {
+ // skip playlist if we already have too many tracks
+ changeTrack(mediaTag, +1);
+ } else {
+ // do not use the cached playlist here, though it is tempting: it might genuinely change to allow for updates
+ fetchPlaylist(
+ trackUrl,
+ () => {
+ playlist.splice(trackIndex, 1, ...playlists[trackUrl]);
+ playlists[playlistUrl] = playlist;
+ updateSrc(mediaTag, callback);
+ },
+ () => callback());
+ }
+ } else {
+ let url = prefetchedTracks.has(trackUrl)
+ ? prefetchedTracks.get(trackUrl) instanceof Blob
+ ? URL.createObjectURL(prefetchedTracks.get(trackUrl))
+ : trackUrl : trackUrl;
+ const oldUrl = mediaTag.getAttribute("src");
+ mediaTag.setAttribute("src", url);
+ // replace the url when done, because a blob from an xhr request
+ // is more reliable in the media tag;
+ // the normal URL caused jumping prematurely to the next track.
+ if (url == trackUrl) {
+ prefetchTrack(trackUrl, () => {
+ if (mediaTag.paused) {
+ if (url == mediaTag.getAttribute("src")) {
+ if (mediaTag.currentTime === 0) {
+ mediaTag.setAttribute("src", URL.createObjectURL(
+ prefetchedTracks.get(url)));
+ }
+ }
+ }
+ });
+ }
+ // allow releasing memory
+ if (isBlob(oldUrl)) {
+ URL.revokeObjectURL(oldUrl);
+ }
+ // update title
+ mediaTag.parentElement.querySelector(".m3u-player--title").title = stripUrlParameters(trackUrl);
+ mediaTag.parentElement.querySelector(".m3u-player--title").textContent = stripUrlParameters(trackUrl);
+ // start prefetching the next three tracks.
+ for (const i of [1, 2, 3]) {
+ if (playlist.length > Number(trackIndex) + i) {
+ prefetchTrack(playlist[Number(trackIndex) + i]);
+ }
+ }
+ callback();
+ }
+}
+function changeTrack(mediaTag, diff) {
+ const currentTrackIndex = Number(mediaTag.getAttribute("track-index"));
+ const nextTrackIndex = currentTrackIndex + diff;
+ const tracks = playlists[mediaTag.getAttribute("playlist")];
+ if (nextTrackIndex >= 0) { // do not collapse the if clauses with double-and, that does not survive inlining
+ if (tracks.length > nextTrackIndex) {
+ mediaTag.setAttribute("track-index", nextTrackIndex);
+ updateSrc(mediaTag, () => mediaTag.play());
+ }
+ }
+}
+
+/**
+ * Turn a media tag into playlist player.
+ */
+function initPlayer(mediaTag) {
+ mediaTag.setAttribute("playlist", replaceHost(mediaTag.getAttribute("src")));
+ mediaTag.setAttribute("track-index", 0);
+ const url = mediaTag.getAttribute("playlist");
+ const wrapper = mediaTag.parentElement.insertBefore(document.createElement("div"), mediaTag);
+ const controls = document.createElement("div");
+ const left = document.createElement("span");
+ const title = document.createElement("span");
+ const right = document.createElement("span");
+ controls.appendChild(left);
+ controls.appendChild(title);
+ controls.appendChild(right);
+ left.classList.add("m3u-player--left");
+ right.classList.add("m3u-player--right");
+ title.classList.add("m3u-player--title");
+ title.style.overflow = "hidden";
+ title.style.textOverflow = "ellipsis";
+ title.style.whiteSpace = "nowrap";
+ title.style.opacity = "0.3";
+ title.style.direction = "rtl"; // for truncation on the left
+ title.style.paddingLeft = "0.5em";
+ title.style.paddingRight = "0.5em";
+ controls.style.display = "flex";
+ controls.style.justifyContent = "space-between";
+ const styleTag = document.createElement("style");
+ styleTag.innerHTML = ".m3u-player--left:hover, .m3u-player--right:hover {color: wheat; background-color: DarkSlateGray}";
+ wrapper.appendChild(styleTag);
+ wrapper.appendChild(controls);
+ controls.style.width = mediaTag.getBoundingClientRect().width.toString() + "px";
+ // appending the media tag to the wrapper removes it from the outer scope but keeps the event listeners
+ wrapper.appendChild(mediaTag);
+ left.innerHTML = "<"; // not textContent, because we MUST escape
+ // the tag here and textContent shows the
+ // escaped version
+ left.onclick = () => changeTrack(mediaTag, -1);
+ right.innerHTML = ">";
+ right.onclick = () => changeTrack(mediaTag, +1);
+ fetchPlaylist(
+ url,
+ () => {
+ updateSrc(mediaTag, () => null);
+ mediaTag.addEventListener("ended", event => {
+ if (mediaTag.currentTime >= mediaTag.duration) {
+ changeTrack(mediaTag, +1);
+ }
+ });
+ },
+ () => null);
+ // keep the controls aligned to the media tag
+ mediaTag.resizeObserver = new ResizeObserver(entries => {
+ controls.style.width = entries[0].contentRect.width.toString() + "px";
+ });
+ mediaTag.resizeObserver.observe(mediaTag);
+}
+function processTag(mediaTag) {
+ if (!mediaTag.canPlayType('audio/x-mpegurl')) {
+ if (isPlaylist(mediaTag.getAttribute("src"))) {
+ initPlayer(mediaTag);
+ }
+ }
+}
+document.addEventListener('DOMContentLoaded', () => {
+ const nodes = document.querySelectorAll("audio,video");
+ nodes.forEach(processTag);
+});
+// @license-end
+// The script:1 ends here
diff --git a/src/freenet/l10n/freenet.l10n.en.properties b/src/freenet/l10n/freenet.l10n.en.properties
index 15a491c3d7e..58fa08a7459 100644
--- a/src/freenet/l10n/freenet.l10n.en.properties
+++ b/src/freenet/l10n/freenet.l10n.en.properties
@@ -1915,6 +1915,8 @@ SimpleToadletServer.metaRefreshSamePageInterval=Allow freesites to refresh thems
SimpleToadletServer.metaRefreshSamePageIntervalLong=Allow freesites to refresh themselves periodically with HTML meta refresh: Minimum interval in seconds or -1 for disabled.
SimpleToadletServer.metaRefreshRedirectInterval=Allow freesites to redirect to other freesites after a delay: Minimum interval in seconds or -1 for disabled.
SimpleToadletServer.metaRefreshRedirectIntervalLong=Allow freesites to redirect to other freesites after a delay with HTML meta refresh: Minimum interval in seconds or -1 for disabled.
+SimpleToadletServer.embedM3uPlayerInFreesites=Embed an m3u player in Freesites?
+SimpleToadletServer.embedM3uPlayerInFreesitesLong=Whether to embed a Javascript-based player checked by the Freenet team which turns video- and audio-tags with m3u-playlists into interactive streaming players.
SimpleToadletServer.panicButton=Show the panic button?
SimpleToadletServer.panicButtonLong=Shows a 'panic button' on the queue page that will remove all downloads and uploads, wipe the cache of recently visited freesites, and clear the master keys file.
SimpleToadletServer.noConfirmPanic=No confirmation on panic button?
diff --git a/test/freenet/client/filter/ContentFilterTest.java b/test/freenet/client/filter/ContentFilterTest.java
index 4d6c95a1099..67e30b5c13f 100644
--- a/test/freenet/client/filter/ContentFilterTest.java
+++ b/test/freenet/client/filter/ContentFilterTest.java
@@ -213,36 +213,36 @@ public void testHTMLFilter() throws Exception {
public void testMetaRefresh() throws Exception {
HTMLFilter.metaRefreshSamePageMinInterval = 5;
HTMLFilter.metaRefreshRedirectMinInterval = 30;
- assertEquals(META_TIME_ONLY, headFilter(META_TIME_ONLY));
- assertEquals(META_TIME_ONLY, headFilter(META_TIME_ONLY_WRONG_CASE));
- assertEquals(META_TIME_ONLY, headFilter(META_TIME_ONLY_TOO_SHORT));
- assertEquals("", headFilter(META_TIME_ONLY_NEGATIVE));
- assertEquals(META_TIME_ONLY_BADNUM_OUT, headFilter(META_TIME_ONLY_BADNUM1));
- assertEquals(META_TIME_ONLY_BADNUM_OUT, headFilter(META_TIME_ONLY_BADNUM2));
- assertEquals(META_VALID_REDIRECT, headFilter(META_VALID_REDIRECT));
- assertEquals(META_VALID_REDIRECT, headFilter(META_VALID_REDIRECT_NOSPACE));
- assertEquals(META_BOGUS_REDIRECT1_OUT, headFilter(META_BOGUS_REDIRECT1));
- assertEquals(META_BOGUS_REDIRECT1_OUT, headFilter(META_BOGUS_REDIRECT2));
- assertEquals(META_BOGUS_REDIRECT3_OUT, headFilter(META_BOGUS_REDIRECT3));
- assertEquals(META_BOGUS_REDIRECT4_OUT, headFilter(META_BOGUS_REDIRECT4));
- assertEquals(META_BOGUS_REDIRECT1_OUT, headFilter(META_BOGUS_REDIRECT5));
- assertEquals(META_BOGUS_REDIRECT_NO_URL, headFilter(META_BOGUS_REDIRECT6));
+ assertEquals(META_TIME_ONLY + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY));
+ assertEquals(META_TIME_ONLY + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_WRONG_CASE));
+ assertEquals(META_TIME_ONLY + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_TOO_SHORT));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_NEGATIVE));
+ assertEquals(META_TIME_ONLY_BADNUM_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_BADNUM1));
+ assertEquals(META_TIME_ONLY_BADNUM_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_BADNUM2));
+ assertEquals(META_VALID_REDIRECT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_VALID_REDIRECT));
+ assertEquals(META_VALID_REDIRECT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_VALID_REDIRECT_NOSPACE));
+ assertEquals(META_BOGUS_REDIRECT1_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT1));
+ assertEquals(META_BOGUS_REDIRECT1_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT2));
+ assertEquals(META_BOGUS_REDIRECT3_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT3));
+ assertEquals(META_BOGUS_REDIRECT4_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT4));
+ assertEquals(META_BOGUS_REDIRECT1_OUT + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT5));
+ assertEquals(META_BOGUS_REDIRECT_NO_URL + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT6));
HTMLFilter.metaRefreshSamePageMinInterval = -1;
HTMLFilter.metaRefreshRedirectMinInterval = -1;
- assertEquals("", headFilter(META_TIME_ONLY));
- assertEquals("", headFilter(META_TIME_ONLY_WRONG_CASE));
- assertEquals("", headFilter(META_TIME_ONLY_TOO_SHORT));
- assertEquals("", headFilter(META_TIME_ONLY_NEGATIVE));
- assertEquals("", headFilter(META_TIME_ONLY_BADNUM1));
- assertEquals("", headFilter(META_TIME_ONLY_BADNUM2));
- assertEquals("", headFilter(META_VALID_REDIRECT));
- assertEquals("", headFilter(META_VALID_REDIRECT_NOSPACE));
- assertEquals("", headFilter(META_BOGUS_REDIRECT1));
- assertEquals("", headFilter(META_BOGUS_REDIRECT2));
- assertEquals("", headFilter(META_BOGUS_REDIRECT3));
- assertEquals("", headFilter(META_BOGUS_REDIRECT4));
- assertEquals("", headFilter(META_BOGUS_REDIRECT5));
- assertEquals("", headFilter(META_BOGUS_REDIRECT6));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_WRONG_CASE));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_TOO_SHORT));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_NEGATIVE));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_BADNUM1));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_TIME_ONLY_BADNUM2));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_VALID_REDIRECT));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_VALID_REDIRECT_NOSPACE));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT1));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT2));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT3));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT4));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT5));
+ assertEquals("" + HTMLFilter.m3uPlayerScriptTagContent(), headFilter(META_BOGUS_REDIRECT6));
}
private String headFilter(String data) throws Exception {