Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.media.remote; | 5 package org.chromium.chrome.browser.media.remote; |
| 6 | 6 |
| 7 import android.content.Context; | 7 import android.content.Context; |
| 8 import android.net.Uri; | 8 import android.net.Uri; |
| 9 import android.os.AsyncTask; | 9 import android.os.AsyncTask; |
| 10 import android.text.TextUtils; | 10 import android.text.TextUtils; |
| 11 import android.util.Log; | 11 import android.util.Log; |
| 12 | 12 |
| 13 import org.apache.http.Header; | |
| 14 import org.apache.http.message.BasicHeader; | |
| 15 import org.chromium.chrome.browser.ChromiumApplication; | 13 import org.chromium.chrome.browser.ChromiumApplication; |
| 16 | 14 |
| 17 import java.io.IOException; | 15 import java.io.IOException; |
| 18 import java.net.HttpURLConnection; | 16 import java.net.HttpURLConnection; |
| 19 import java.net.MalformedURLException; | 17 import java.net.MalformedURLException; |
| 20 import java.net.URI; | 18 import java.net.URI; |
| 21 import java.net.URISyntaxException; | 19 import java.net.URISyntaxException; |
| 22 import java.net.URL; | 20 import java.net.URL; |
| 23 import java.util.Arrays; | 21 import java.util.List; |
| 22 import java.util.Map; | |
| 24 | 23 |
| 25 /** | 24 /** |
| 26 * Resolves the final URL if it's a redirect. Works asynchronously, uses HTTP | 25 * Resolves the final URL if it's a redirect. Works asynchronously, uses HTTP |
| 27 * HEAD request to determine if the URL is redirected. | 26 * HEAD request to determine if the URL is redirected. |
| 28 */ | 27 */ |
| 29 public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res ult> { | 28 public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res ult> { |
| 30 | 29 |
| 31 /** | 30 /** |
| 32 * The interface to get the initial URI with cookies from and pass the final | 31 * The interface to get the initial URI with cookies from and pass the final |
| 33 * URI to. | 32 * URI to. |
| 34 */ | 33 */ |
| 35 public interface Delegate { | 34 public interface Delegate { |
| 36 /** | 35 /** |
| 37 * @return the original URL to resolve. | 36 * @return the original URL to resolve. |
| 38 */ | 37 */ |
| 39 Uri getUri(); | 38 Uri getUri(); |
| 40 | 39 |
| 41 /** | 40 /** |
| 42 * @return the cookies to fetch the URL with. | 41 * @return the cookies to fetch the URL with. |
| 43 */ | 42 */ |
| 44 String getCookies(); | 43 String getCookies(); |
| 45 | 44 |
| 46 /** | 45 /** |
| 47 * Passes the resolved URL to the delegate. | 46 * Passes the resolved URL to the delegate. |
| 48 * | 47 * |
| 49 * @param uri the resolved URL. | 48 * @param uri the resolved URL. |
| 50 */ | 49 */ |
| 51 void setUri(Uri uri, Header[] headers); | 50 void setUri(Uri uri, Map<String, List<String>> map); |
| 52 } | 51 } |
| 53 | 52 |
| 54 | 53 |
| 55 protected static final class Result { | 54 protected static final class Result { |
| 56 private final String mUri; | 55 private final String mUri; |
| 57 private final Header[] mRelevantHeaders; | 56 private final Map<String, List<String>> mHeaders; |
| 58 | 57 |
| 59 public Result(String uri, Header[] relevantHeaders) { | 58 public Result(String uri, Map<String, List<String>> headers) { |
| 60 mUri = uri; | 59 mUri = uri; |
| 61 mRelevantHeaders = | 60 mHeaders = headers; |
| 62 relevantHeaders != null | |
| 63 ? Arrays.copyOf(relevantHeaders, relevantHeaders.length) | |
| 64 : null; | |
| 65 } | 61 } |
| 66 | 62 |
| 67 public String getUri() { | 63 public String getUri() { |
| 68 return mUri; | 64 return mUri; |
| 69 } | 65 } |
| 70 | 66 |
| 71 public Header[] getRelevantHeaders() { | 67 public Map<String, List<String>> getHeaders() { |
|
whywhat
2015/03/30 15:51:34
I thought you were going to turn this array into a
aberent
2015/03/30 17:24:04
Done.
| |
| 72 return mRelevantHeaders != null | 68 return mHeaders; |
| 73 ? Arrays.copyOf(mRelevantHeaders, mRelevantHeaders.length) | |
| 74 : null; | |
| 75 } | 69 } |
| 76 } | 70 } |
| 77 | 71 |
| 78 private static final String TAG = "MediaUrlResolver"; | 72 private static final String TAG = "MediaUrlResolver"; |
| 79 | 73 |
| 80 private static final String CORS_HEADER_NAME = "Access-Control-Allow-Origin" ; | |
| 81 private static final String COOKIES_HEADER_NAME = "Cookies"; | 74 private static final String COOKIES_HEADER_NAME = "Cookies"; |
| 82 private static final String USER_AGENT_HEADER_NAME = "User-Agent"; | 75 private static final String USER_AGENT_HEADER_NAME = "User-Agent"; |
| 83 private static final String RANGE_HEADER_NAME = "Range"; | 76 private static final String RANGE_HEADER_NAME = "Range"; |
| 84 | 77 |
| 85 // We don't want to necessarily fetch the whole video but we don't want to m iss the CORS header. | 78 // We don't want to necessarily fetch the whole video but we don't want to m iss the CORS header. |
| 86 // Assume that 64k should be more than enough to keep all the headers. | 79 // Assume that 64k should be more than enough to keep all the headers. |
| 87 private static final String RANGE_HEADER_VALUE = "bytes: 0-65536"; | 80 private static final String RANGE_HEADER_VALUE = "bytes: 0-65536"; |
| 88 | 81 |
| 89 private final Context mContext; | 82 private final Context mContext; |
| 90 private final Delegate mDelegate; | 83 private final Delegate mDelegate; |
| 91 | 84 |
| 92 /** | 85 /** |
| 93 * The constructor | 86 * The constructor |
| 94 * @param context the context to use to resolve the URL | 87 * @param context the context to use to resolve the URL |
| 95 * @param delegate The customer for this URL resolver. | 88 * @param delegate The customer for this URL resolver. |
| 96 */ | 89 */ |
| 97 public MediaUrlResolver(Context context, Delegate delegate) { | 90 public MediaUrlResolver(Context context, Delegate delegate) { |
| 98 mContext = context; | 91 mContext = context; |
| 99 mDelegate = delegate; | 92 mDelegate = delegate; |
| 100 } | 93 } |
| 101 | 94 |
| 102 @Override | 95 @Override |
| 103 protected MediaUrlResolver.Result doInBackground(Void... params) { | 96 protected MediaUrlResolver.Result doInBackground(Void... params) { |
| 104 Uri uri = mDelegate.getUri(); | 97 Uri uri = mDelegate.getUri(); |
| 105 String url = uri.toString(); | 98 String url = uri.toString(); |
| 106 Header[] relevantHeaders = null; | |
| 107 String cookies = mDelegate.getCookies(); | 99 String cookies = mDelegate.getCookies(); |
| 108 String userAgent = ChromiumApplication.getBrowserUserAgent(); | 100 String userAgent = ChromiumApplication.getBrowserUserAgent(); |
| 109 // URL may already be partially percent encoded; double percent encoding will break | 101 // URL may already be partially percent encoded; double percent encoding will break |
| 110 // things, so decode it before sanitizing it. | 102 // things, so decode it before sanitizing it. |
| 111 String sanitizedUrl = sanitizeUrl(Uri.decode(url)); | 103 String sanitizedUrl = sanitizeUrl(Uri.decode(url)); |
| 104 Map<String, List<String>> headers = null; | |
| 112 | 105 |
| 113 // If we failed to sanitize the URL (e.g. because the host name contains underscores) then | 106 // If we failed to sanitize the URL (e.g. because the host name contains underscores) then |
| 114 // HttpURLConnection won't work,so we can't follow redirections. Just tr y to use it as is. | 107 // HttpURLConnection won't work,so we can't follow redirections. Just tr y to use it as is. |
| 115 // TODO (aberent): Find out if there is a way of following redirections that is not so | 108 // TODO (aberent): Find out if there is a way of following redirections that is not so |
| 116 // strict on the URL format. | 109 // strict on the URL format. |
| 117 if (!sanitizedUrl.equals("")) { | 110 if (!sanitizedUrl.equals("")) { |
| 118 HttpURLConnection urlConnection = null; | 111 HttpURLConnection urlConnection = null; |
| 119 try { | 112 try { |
| 120 URL requestUrl = new URL(sanitizedUrl); | 113 URL requestUrl = new URL(sanitizedUrl); |
| 121 urlConnection = (HttpURLConnection) requestUrl.openConnection(); | 114 urlConnection = (HttpURLConnection) requestUrl.openConnection(); |
| 122 if (!TextUtils.isEmpty(cookies)) { | 115 if (!TextUtils.isEmpty(cookies)) { |
| 123 urlConnection.setRequestProperty(COOKIES_HEADER_NAME, cookie s); | 116 urlConnection.setRequestProperty(COOKIES_HEADER_NAME, cookie s); |
| 124 } | 117 } |
| 125 urlConnection.setRequestProperty(USER_AGENT_HEADER_NAME, userAge nt); | 118 urlConnection.setRequestProperty(USER_AGENT_HEADER_NAME, userAge nt); |
| 126 urlConnection.setRequestProperty(RANGE_HEADER_NAME, RANGE_HEADER _VALUE); | 119 urlConnection.setRequestProperty(RANGE_HEADER_NAME, RANGE_HEADER _VALUE); |
| 127 | 120 |
| 128 // This triggers resolving the URL and receiving the headers. | 121 // This triggers resolving the URL and receiving the headers. |
| 129 urlConnection.getHeaderFields(); | 122 urlConnection.getHeaderFields(); |
| 130 | 123 |
| 131 url = urlConnection.getURL().toString(); | 124 url = urlConnection.getURL().toString(); |
| 132 String corsHeader = urlConnection.getHeaderField(CORS_HEADER_NAM E); | 125 headers = urlConnection.getHeaderFields(); |
| 133 if (corsHeader != null) { | |
| 134 relevantHeaders = new Header[1]; | |
| 135 relevantHeaders[0] = new BasicHeader(CORS_HEADER_NAME, corsH eader); | |
| 136 } | |
| 137 } catch (IOException e) { | 126 } catch (IOException e) { |
| 138 Log.e(TAG, "Failed to fetch the final URI", e); | 127 Log.e(TAG, "Failed to fetch the final URI", e); |
| 139 url = ""; | 128 url = ""; |
| 140 } | 129 } |
| 141 if (urlConnection != null) urlConnection.disconnect(); | 130 if (urlConnection != null) urlConnection.disconnect(); |
| 142 } | 131 } |
| 143 return new MediaUrlResolver.Result(url, relevantHeaders); | 132 return new MediaUrlResolver.Result(url, headers); |
| 144 } | 133 } |
| 145 | 134 |
| 146 @Override | 135 @Override |
| 147 protected void onPostExecute(MediaUrlResolver.Result result) { | 136 protected void onPostExecute(MediaUrlResolver.Result result) { |
| 148 String url = result.getUri(); | 137 String url = result.getUri(); |
| 149 Uri uri = "".equals(url) ? Uri.EMPTY : Uri.parse(url); | 138 Uri uri = "".equals(url) ? Uri.EMPTY : Uri.parse(url); |
| 150 mDelegate.setUri(uri, result.getRelevantHeaders()); | 139 mDelegate.setUri(uri, result.getHeaders()); |
| 151 } | 140 } |
| 152 | 141 |
| 153 private String sanitizeUrl(String unsafeUrl) { | 142 private String sanitizeUrl(String unsafeUrl) { |
| 154 URL url; | 143 URL url; |
| 155 URI uri; | 144 URI uri; |
| 156 try { | 145 try { |
| 157 url = new URL(unsafeUrl); | 146 url = new URL(unsafeUrl); |
| 158 uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), u rl.getPort(), | 147 uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), u rl.getPort(), |
| 159 url.getPath(), url.getQuery(), url.getRef()); | 148 url.getPath(), url.getQuery(), url.getRef()); |
| 160 return uri.toURL().toString(); | 149 return uri.toURL().toString(); |
| 161 } catch (URISyntaxException syntaxException) { | 150 } catch (URISyntaxException syntaxException) { |
| 162 Log.w(TAG, "URISyntaxException " + syntaxException); | 151 Log.w(TAG, "URISyntaxException " + syntaxException); |
| 163 } catch (MalformedURLException malformedUrlException) { | 152 } catch (MalformedURLException malformedUrlException) { |
| 164 Log.w(TAG, "MalformedURLException " + malformedUrlException); | 153 Log.w(TAG, "MalformedURLException " + malformedUrlException); |
| 165 } | 154 } |
| 166 return ""; | 155 return ""; |
| 167 } | 156 } |
| 168 } | 157 } |
| OLD | NEW |