Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c446dc32 authored by Lajos Molnar's avatar Lajos Molnar
Browse files

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
parent 016db948
Loading
Loading
Loading
Loading
+97 −12
Original line number Diff line number Diff line
@@ -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<String, String> convertHeaderStringToMap(String headers) {
        HashMap<String, String> map = new HashMap<String, String>();

@@ -78,9 +107,11 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                String key = pair.substring(0, colonPos);
                String val = pair.substring(colonPos + 1);

                if (!filterOutInternalHeaders(key, val)) {
                    map.put(key, val);
                }
            }
        }

        return map;
    }
@@ -107,7 +138,14 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
        teardownConnection();

        try {
            mConnection = (HttpURLConnection)mURL.openConnection();
            int response;
            int redirectCount = 0;

            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<String, String> entry : mHeaders.entrySet()) {
@@ -121,9 +159,53 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                            "Range", "bytes=" + offset + "-");
                }

            int response = mConnection.getResponseCode();
            // remember the current, possibly redirected URL
                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 (mAllowCrossDomainRedirect) {
                // remember the current, potentially redirected URL if redirects
                // were handled by HttpURLConnection
                mURL = mConnection.getURL();
            }

            if (response == HttpURLConnection.HTTP_PARTIAL) {
                // Partial content, we cannot just use getContentLength
@@ -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");