From c446dc32742de388b95ff7cf97f4dc1576cb57c5 Mon Sep 17 00:00:00 2001 From: Lajos Molnar Date: Mon, 28 Apr 2014 16:16:54 -0700 Subject: [PATCH] MediaHTTPConnection: support header android-allow-cross-domain-redirect If present and set to false, media http redirects across domains will not be followed. As long as domains are identical, redirects across protocols or ports will still be followed. Also fail more seriously if redirection fails or is not supported, so that media client does not keep retrying the connection. Bug: 12573548 Change-Id: Ifd2539ad3a90f669d43bd0e82845dbc8ae0b4a3e --- .../android/media/MediaHTTPConnection.java | 109 ++++++++++++++++-- 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index eb91668ca8c77..d3b1520566093 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -28,9 +28,12 @@ import java.net.CookieManager; import java.net.URL; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.NoRouteToHostException; import java.util.HashMap; import java.util.Map; +import static android.media.MediaPlayer.MEDIA_ERROR_UNSUPPORTED; + /** @hide */ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { private static final String TAG = "MediaHTTPConnection"; @@ -43,6 +46,12 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { private long mTotalSize = -1; private InputStream mInputStream = null; + private boolean mAllowCrossDomainRedirect = true; + + // from com.squareup.okhttp.internal.http + private final static int HTTP_TEMP_REDIRECT = 307; + private final static int MAX_REDIRECTS = 20; + public MediaHTTPConnection() { if (CookieHandler.getDefault() == null) { CookieHandler.setDefault(new CookieManager()); @@ -59,6 +68,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { try { disconnect(); + mAllowCrossDomainRedirect = true; mURL = new URL(uri); mHeaders = convertHeaderStringToMap(headers); } catch (MalformedURLException e) { @@ -68,6 +78,25 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { return native_getIMemory(); } + private boolean parseBoolean(String val) { + try { + return Long.parseLong(val) != 0; + } catch (NumberFormatException e) { + return "true".equalsIgnoreCase(val) || + "yes".equalsIgnoreCase(val); + } + } + + /* returns true iff header is internal */ + private boolean filterOutInternalHeaders(String key, String val) { + if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) { + mAllowCrossDomainRedirect = parseBoolean(val); + } else { + return false; + } + return true; + } + private Map convertHeaderStringToMap(String headers) { HashMap map = new HashMap(); @@ -78,7 +107,9 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { String key = pair.substring(0, colonPos); String val = pair.substring(colonPos + 1); - map.put(key, val); + if (!filterOutInternalHeaders(key, val)) { + map.put(key, val); + } } } @@ -107,24 +138,75 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { teardownConnection(); try { - mConnection = (HttpURLConnection)mURL.openConnection(); + int response; + int redirectCount = 0; - if (mHeaders != null) { - for (Map.Entry entry : mHeaders.entrySet()) { + URL url = mURL; + while (true) { + mConnection = (HttpURLConnection)url.openConnection(); + // handle redirects ourselves if we do not allow cross-domain redirect + mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect); + + if (mHeaders != null) { + for (Map.Entry entry : mHeaders.entrySet()) { + mConnection.setRequestProperty( + entry.getKey(), entry.getValue()); + } + } + + if (offset > 0) { mConnection.setRequestProperty( - entry.getKey(), entry.getValue()); + "Range", "bytes=" + offset + "-"); + } + + response = mConnection.getResponseCode(); + if (response != HttpURLConnection.HTTP_MULT_CHOICE && + response != HttpURLConnection.HTTP_MOVED_PERM && + response != HttpURLConnection.HTTP_MOVED_TEMP && + response != HttpURLConnection.HTTP_SEE_OTHER && + response != HTTP_TEMP_REDIRECT) { + // not a redirect, or redirect handled by HttpURLConnection + break; + } + + if (++redirectCount > MAX_REDIRECTS) { + throw new NoRouteToHostException("Too many redirects: " + redirectCount); + } + + String method = mConnection.getRequestMethod(); + if (response == HTTP_TEMP_REDIRECT && + !method.equals("GET") && !method.equals("HEAD")) { + // "If the 307 status code is received in response to a + // request other than GET or HEAD, the user agent MUST NOT + // automatically redirect the request" + throw new NoRouteToHostException("Invalid redirect"); + } + String location = mConnection.getHeaderField("Location"); + if (location == null) { + throw new NoRouteToHostException("Invalid redirect"); + } + url = new URL(mURL /* TRICKY: don't use url! */, location); + if (!url.getProtocol().equals("https") && + !url.getProtocol().equals("http")) { + throw new NoRouteToHostException("Unsupported protocol redirect"); + } + boolean sameHost = mURL.getHost().equals(url.getHost()); + if (!sameHost) { + throw new NoRouteToHostException("Cross-domain redirects are disallowed"); + } + + if (response != HTTP_TEMP_REDIRECT) { + // update effective URL, unless it is a Temporary Redirect + mURL = url; } } - if (offset > 0) { - mConnection.setRequestProperty( - "Range", "bytes=" + offset + "-"); + if (mAllowCrossDomainRedirect) { + // remember the current, potentially redirected URL if redirects + // were handled by HttpURLConnection + mURL = mConnection.getURL(); } - int response = mConnection.getResponseCode(); - // remember the current, possibly redirected URL - mURL = mConnection.getURL(); - if (response == HttpURLConnection.HTTP_PARTIAL) { // Partial content, we cannot just use getContentLength // because what we want is not just the length of the range @@ -207,6 +289,9 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { } return n; + } catch (NoRouteToHostException e) { + Log.w(TAG, "readAt " + offset + " / " + size + " => " + e); + return MEDIA_ERROR_UNSUPPORTED; } catch (IOException e) { if (VERBOSE) { Log.d(TAG, "readAt " + offset + " / " + size + " => -1");