package android.media;
import android.net.NetworkUtils;
import android.os.StrictMode;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.Proxy;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.NoRouteToHostException;
import java.net.ProtocolException;
import java.net.UnknownServiceException;
import java.util.HashMap;
import java.util.Map;
import static android.media.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
public class Media2HTTPConnection {
private static final String TAG = "Media2HTTPConnection";
private static final boolean VERBOSE = false;
private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
private long mCurrentOffset = -1;
private URL mURL = null;
private Map<String, String> mHeaders = null;
private HttpURLConnection mConnection = null;
private long mTotalSize = -1;
private InputStream mInputStream = null;
private boolean mAllowCrossDomainRedirect = true;
private boolean mAllowCrossProtocolRedirect = true;
private final static int HTTP_TEMP_REDIRECT = 307;
private final static int MAX_REDIRECTS = 20;
public Media2HTTPConnection() {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler == null) {
Log.w(TAG, "Media2HTTPConnection: Unexpected. No CookieHandler found.");
}
}
public boolean connect(String uri, String headers) {
if (VERBOSE) {
Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
}
try {
disconnect();
mAllowCrossDomainRedirect = true;
mURL = new URL(uri);
mHeaders = convertHeaderStringToMap(headers);
} catch (MalformedURLException e) {
return false;
}
return true;
}
private boolean parseBoolean(String val) {
try {
return Long.parseLong(val) != 0;
} catch (NumberFormatException e) {
return "true".equalsIgnoreCase(val) ||
"yes".equalsIgnoreCase(val);
}
}
private boolean filterOutInternalHeaders(String key, String val) {
if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
mAllowCrossDomainRedirect = parseBoolean(val);
mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
} else {
return false;
}
return true;
}
private Map<String, String> convertHeaderStringToMap(String headers) {
HashMap<String, String> map = new HashMap<String, String>();
String[] pairs = headers.split("\r\n");
for (String pair : pairs) {
int colonPos = pair.indexOf(":");
if (colonPos >= 0) {
String key = pair.substring(0, colonPos);
String val = pair.substring(colonPos + 1);
if (!filterOutInternalHeaders(key, val)) {
map.put(key, val);
}
}
}
return map;
}
public void disconnect() {
teardownConnection();
mHeaders = null;
mURL = null;
}
private void teardownConnection() {
if (mConnection != null) {
if (mInputStream != null) {
try {
mInputStream.close();
} catch (IOException e) {
}
mInputStream = null;
}
mConnection.disconnect();
mConnection = null;
mCurrentOffset = -1;
}
}
private static final boolean isLocalHost(URL url) {
if (url == null) {
return false;
}
String host = url.getHost();
if (host == null) {
return false;
}
try {
if (host.equalsIgnoreCase("localhost")) {
return true;
}
if (NetworkUtils.numericToInetAddress(host).isLoopbackAddress()) {
return true;
}
} catch (IllegalArgumentException iex) {
}
return false;
}
private void seekTo(long offset) throws IOException {
teardownConnection();
try {
int response;
int redirectCount = 0;
URL url = mURL;
boolean noProxy = isLocalHost(url);
while (true) {
if (noProxy) {
mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
} else {
mConnection = (HttpURLConnection)url.openConnection();
}
mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
if (mHeaders != null) {
for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
mConnection.setRequestProperty(
entry.getKey(), entry.getValue());
}
}
if (offset > 0) {
mConnection.setRequestProperty(
"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) {
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")) {
throw new NoRouteToHostException("Invalid redirect");
}
String location = mConnection.getHeaderField("Location");
if (location == null) {
throw new NoRouteToHostException("Invalid redirect");
}
url = new URL(mURL , location);
if (!url.getProtocol().equals("https") &&
!url.getProtocol().equals("http")) {
throw new NoRouteToHostException("Unsupported protocol redirect");
}
boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
if (!mAllowCrossProtocolRedirect && !sameProtocol) {
throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
}
boolean sameHost = mURL.getHost().equals(url.getHost());
if (!mAllowCrossDomainRedirect && !sameHost) {
throw new NoRouteToHostException("Cross-domain redirects are disallowed");
}
if (response != HTTP_TEMP_REDIRECT) {
mURL = url;
}
}
if (mAllowCrossDomainRedirect) {
mURL = mConnection.getURL();
}
if (response == HttpURLConnection.HTTP_PARTIAL) {
String contentRange =
mConnection.getHeaderField("Content-Range");
mTotalSize = -1;
if (contentRange != null) {
int lastSlashPos = contentRange.lastIndexOf('/');
if (lastSlashPos >= 0) {
String total =
contentRange.substring(lastSlashPos + 1);
try {
mTotalSize = Long.parseLong(total);
} catch (NumberFormatException e) {
}
}
}
} else if (response != HttpURLConnection.HTTP_OK) {
throw new IOException();
} else {
mTotalSize = mConnection.getContentLength();
}
if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
throw new ProtocolException();
}
mInputStream =
new BufferedInputStream(mConnection.getInputStream());
mCurrentOffset = offset;
} catch (IOException e) {
mTotalSize = -1;
teardownConnection();
mCurrentOffset = -1;
throw e;
}
}
public int readAt(long offset, byte[] data, int size) {
StrictMode.ThreadPolicy policy =
new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
try {
if (offset != mCurrentOffset) {
seekTo(offset);
}
int n = mInputStream.read(data, 0, size);
if (n == -1) {
n = 0;
}
mCurrentOffset += n;
if (VERBOSE) {
Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
}
return n;
} catch (ProtocolException e) {
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
return MEDIA_ERROR_UNSUPPORTED;
} catch (NoRouteToHostException e) {
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
return MEDIA_ERROR_UNSUPPORTED;
} catch (UnknownServiceException e) {
Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
return MEDIA_ERROR_UNSUPPORTED;
} catch (IOException e) {
if (VERBOSE) {
Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
}
return -1;
} catch (Exception e) {
if (VERBOSE) {
Log.d(TAG, "unknown exception " + e);
Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
}
return -1;
}
}
public long getSize() {
if (mConnection == null) {
try {
seekTo(0);
} catch (IOException e) {
return -1;
}
}
return mTotalSize;
}
public String getMIMEType() {
if (mConnection == null) {
try {
seekTo(0);
} catch (IOException e) {
return "application/octet-stream";
}
}
return mConnection.getContentType();
}
public String getUri() {
return mURL.toString();
}
}