mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
parent
4f039eaafa
commit
a6debf4904
@ -36,21 +36,16 @@ import androidx.media3.common.PlaybackException;
|
|||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.ConditionVariable;
|
import androidx.media3.common.util.ConditionVariable;
|
||||||
import androidx.media3.common.util.Log;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.net.HttpHeaders;
|
import com.google.common.net.HttpHeaders;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.CookieHandler;
|
|
||||||
import java.net.CookieManager;
|
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -937,6 +932,14 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String parseCookies(@Nullable List<String> setCookieHeaders) {
|
||||||
|
if (setCookieHeaders == null || setCookieHeaders.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return TextUtils.join(";", setCookieHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getFirstHeader(Map<String, List<String>> allHeaders, String headerName) {
|
private static String getFirstHeader(Map<String, List<String>> allHeaders, String headerName) {
|
||||||
@Nullable List<String> headers = allHeaders.get(headerName);
|
@Nullable List<String> headers = allHeaders.get(headerName);
|
||||||
@ -958,8 +961,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
* A wrapper class that manages a {@link UrlRequest} and the {@link UrlRequestCallback} associated
|
* A wrapper class that manages a {@link UrlRequest} and the {@link UrlRequestCallback} associated
|
||||||
* with that request.
|
* with that request.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
private static final class UrlRequestWrapper {
|
||||||
/* private */ static final class UrlRequestWrapper {
|
|
||||||
|
|
||||||
private final UrlRequest urlRequest;
|
private final UrlRequest urlRequest;
|
||||||
private final UrlRequestCallback urlRequestCallback;
|
private final UrlRequestCallback urlRequestCallback;
|
||||||
@ -1002,21 +1004,14 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
private final class UrlRequestCallback implements UrlRequest.Callback {
|
||||||
/* private */ final class UrlRequestCallback implements UrlRequest.Callback {
|
|
||||||
|
|
||||||
private static final String TAG = "HttpEngineDataSource";
|
|
||||||
private volatile boolean isClosed = false;
|
private volatile boolean isClosed = false;
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
this.isClosed = true;
|
this.isClosed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("argument.type.incompatible")
|
|
||||||
private void resetDefaultCookieHandler() {
|
|
||||||
CookieHandler.setDefault(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onRedirectReceived(
|
public synchronized void onRedirectReceived(
|
||||||
UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
|
UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
|
||||||
@ -1045,21 +1040,6 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
resetConnectTimeout();
|
resetConnectTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasDefaultCookieHandler = CookieHandler.getDefault() != null;
|
|
||||||
if (!hasDefaultCookieHandler && handleSetCookieRequests) {
|
|
||||||
// a temporary CookieManager is created for the duration of this request - this guarantees
|
|
||||||
// redirects preserve the cookies correctly.
|
|
||||||
CookieManager cm = new CookieManager();
|
|
||||||
CookieHandler.setDefault(cm);
|
|
||||||
}
|
|
||||||
|
|
||||||
storeCookiesFromHeaders(info);
|
|
||||||
String cookieHeaders = getCookieHeader(info.getUrl(), info.getHeaders().getAsMap());
|
|
||||||
|
|
||||||
if (!hasDefaultCookieHandler && handleSetCookieRequests) {
|
|
||||||
resetDefaultCookieHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean shouldKeepPost =
|
boolean shouldKeepPost =
|
||||||
keepPostFor302Redirects
|
keepPostFor302Redirects
|
||||||
&& dataSpec.httpMethod == DataSpec.HTTP_METHOD_POST
|
&& dataSpec.httpMethod == DataSpec.HTTP_METHOD_POST
|
||||||
@ -1067,12 +1047,17 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
|
|
||||||
// request.followRedirect() transforms a POST request into a GET request, so if we want to
|
// request.followRedirect() transforms a POST request into a GET request, so if we want to
|
||||||
// keep it as a POST we need to fall through to the manual redirect logic below.
|
// keep it as a POST we need to fall through to the manual redirect logic below.
|
||||||
if (!shouldKeepPost) {
|
if (!shouldKeepPost && !handleSetCookieRequests) {
|
||||||
// No cookies, or we're not handling them - so just follow the redirect.
|
request.followRedirect();
|
||||||
if (!handleSetCookieRequests || TextUtils.isEmpty(cookieHeaders)) {
|
return;
|
||||||
request.followRedirect();
|
}
|
||||||
return;
|
|
||||||
}
|
@Nullable
|
||||||
|
String cookieHeadersValue =
|
||||||
|
parseCookies(info.getHeaders().getAsMap().get(HttpHeaders.SET_COOKIE));
|
||||||
|
if (!shouldKeepPost && TextUtils.isEmpty(cookieHeadersValue)) {
|
||||||
|
request.followRedirect();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.cancel();
|
request.cancel();
|
||||||
@ -1090,15 +1075,13 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
} else {
|
} else {
|
||||||
redirectUrlDataSpec = dataSpec.withUri(Uri.parse(newLocationUrl));
|
redirectUrlDataSpec = dataSpec.withUri(Uri.parse(newLocationUrl));
|
||||||
}
|
}
|
||||||
|
if (!TextUtils.isEmpty(cookieHeadersValue)) {
|
||||||
if (!TextUtils.isEmpty(cookieHeaders)) {
|
|
||||||
Map<String, String> requestHeaders = new HashMap<>();
|
Map<String, String> requestHeaders = new HashMap<>();
|
||||||
requestHeaders.putAll(dataSpec.httpRequestHeaders);
|
requestHeaders.putAll(dataSpec.httpRequestHeaders);
|
||||||
requestHeaders.put(HttpHeaders.COOKIE, cookieHeaders);
|
requestHeaders.put(HttpHeaders.COOKIE, cookieHeadersValue);
|
||||||
redirectUrlDataSpec =
|
redirectUrlDataSpec =
|
||||||
redirectUrlDataSpec.buildUpon().setHttpRequestHeaders(requestHeaders).build();
|
redirectUrlDataSpec.buildUpon().setHttpRequestHeaders(requestHeaders).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlRequestWrapper redirectUrlRequestWrapper;
|
UrlRequestWrapper redirectUrlRequestWrapper;
|
||||||
try {
|
try {
|
||||||
redirectUrlRequestWrapper = buildRequestWrapper(redirectUrlDataSpec);
|
redirectUrlRequestWrapper = buildRequestWrapper(redirectUrlDataSpec);
|
||||||
@ -1113,56 +1096,11 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
|
|||||||
currentUrlRequestWrapper.start();
|
currentUrlRequestWrapper.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the cookie headers from the response in the default {@link CookieHandler}.
|
|
||||||
*
|
|
||||||
* <p>This is a no-op if the {@link CookieHandler} is not set .
|
|
||||||
*/
|
|
||||||
static void storeCookiesFromHeaders(UrlResponseInfo info) {
|
|
||||||
CookieHandler cookieHandler = CookieHandler.getDefault();
|
|
||||||
if (cookieHandler != null) {
|
|
||||||
try {
|
|
||||||
cookieHandler.put(new URI(info.getUrl()), info.getHeaders().getAsMap());
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "Failed to store cookies in CookieHandler", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getCookieHeader(String url) {
|
|
||||||
return getCookieHeader(url, ImmutableMap.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCookieHeader maps Set-Cookie2 (RFC 2965) to Cookie just like CookieManager does.
|
|
||||||
static String getCookieHeader(String url, Map<String, List<String>> headers) {
|
|
||||||
CookieHandler cookieHandler = CookieHandler.getDefault();
|
|
||||||
if (cookieHandler == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, List<String>> cookieHeaders = ImmutableMap.of();
|
|
||||||
try {
|
|
||||||
cookieHeaders = cookieHandler.get(new URI(url), headers);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "Failed to read cookies from CookieHandler", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder cookies = new StringBuilder();
|
|
||||||
if (cookieHeaders.containsKey(HttpHeaders.COOKIE)) {
|
|
||||||
List<String> cookiesList = cookieHeaders.get(HttpHeaders.COOKIE);
|
|
||||||
for (String cookie : cookiesList) {
|
|
||||||
cookies.append(cookie).append("; ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookies.toString().stripTrailing();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
|
public synchronized void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
|
||||||
if (isClosed) {
|
if (isClosed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
storeCookiesFromHeaders(info);
|
|
||||||
responseInfo = info;
|
responseInfo = info;
|
||||||
operation.open();
|
operation.open();
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,9 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
|
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
|
||||||
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
|
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.CookieHandler;
|
|
||||||
import java.net.CookieManager;
|
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -85,14 +80,8 @@ public final class HttpEngineDataSourceTest {
|
|||||||
|
|
||||||
private static final int TEST_CONNECT_TIMEOUT_MS = 100;
|
private static final int TEST_CONNECT_TIMEOUT_MS = 100;
|
||||||
private static final int TEST_READ_TIMEOUT_MS = 100;
|
private static final int TEST_READ_TIMEOUT_MS = 100;
|
||||||
private static final String TEST_URL = "http://google.com/video/";
|
private static final String TEST_URL = "http://google.com";
|
||||||
private static final String TEST_CONTENT_TYPE = "test/test";
|
private static final String TEST_CONTENT_TYPE = "test/test";
|
||||||
private static final String TEST_REQUEST_COOKIE = "foo=bar";
|
|
||||||
private static final String TEST_REQUEST_COOKIE_2 = "baz=qux";
|
|
||||||
private static final String TEST_RESPONSE_SET_COOKIE =
|
|
||||||
TEST_REQUEST_COOKIE + ";path=/video; expires 31-12-2099 23:59:59 GMT";
|
|
||||||
private static final String TEST_RESPONSE_SET_COOKIE_2 =
|
|
||||||
TEST_REQUEST_COOKIE_2 + ";path=/; expires 31-12-2099 23:59:59 GMT";
|
|
||||||
private static final byte[] TEST_POST_BODY = Util.getUtf8Bytes("test post body");
|
private static final byte[] TEST_POST_BODY = Util.getUtf8Bytes("test post body");
|
||||||
private static final long TEST_CONTENT_LENGTH = 16000L;
|
private static final long TEST_CONTENT_LENGTH = 16000L;
|
||||||
|
|
||||||
@ -152,8 +141,6 @@ public final class HttpEngineDataSourceTest {
|
|||||||
// This value can be anything since the DataSpec is unset.
|
// This value can be anything since the DataSpec is unset.
|
||||||
testResponseHeader.put("Content-Length", Long.toString(TEST_CONTENT_LENGTH));
|
testResponseHeader.put("Content-Length", Long.toString(TEST_CONTENT_LENGTH));
|
||||||
testUrlResponseInfo = createUrlResponseInfo(/* statusCode= */ 200);
|
testUrlResponseInfo = createUrlResponseInfo(/* statusCode= */ 200);
|
||||||
|
|
||||||
CookieHandler.setDefault(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -285,15 +272,15 @@ public final class HttpEngineDataSourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void requestHeadersSet() throws HttpDataSourceException {
|
public void requestHeadersSet() throws HttpDataSourceException {
|
||||||
Map<String, String> headersSet = new HashMap<>();
|
Map<String, String> headersSet = new HashMap<>();
|
||||||
when(mockUrlRequestBuilder.addHeader(
|
doAnswer(
|
||||||
ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
|
|
||||||
.thenAnswer(
|
|
||||||
(invocation) -> {
|
(invocation) -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
String value = invocation.getArgument(1);
|
String value = invocation.getArgument(1);
|
||||||
headersSet.put(key, value);
|
headersSet.put(key, value);
|
||||||
return null;
|
return null;
|
||||||
});
|
})
|
||||||
|
.when(mockUrlRequestBuilder)
|
||||||
|
.addHeader(ArgumentMatchers.anyString(), ArgumentMatchers.anyString());
|
||||||
|
|
||||||
dataSourceUnderTest.setRequestProperty("defaultHeader2", "dataSourceOverridesDefault");
|
dataSourceUnderTest.setRequestProperty("defaultHeader2", "dataSourceOverridesDefault");
|
||||||
dataSourceUnderTest.setRequestProperty("dataSourceHeader1", "dataSourceValue1");
|
dataSourceUnderTest.setRequestProperty("dataSourceHeader1", "dataSourceValue1");
|
||||||
@ -460,7 +447,8 @@ public final class HttpEngineDataSourceTest {
|
|||||||
assertThat(e).isInstanceOf(HttpDataSource.InvalidContentTypeException.class);
|
assertThat(e).isInstanceOf(HttpDataSource.InvalidContentTypeException.class);
|
||||||
// Check for connection not automatically closed.
|
// Check for connection not automatically closed.
|
||||||
verify(mockUrlRequest, never()).cancel();
|
verify(mockUrlRequest, never()).cancel();
|
||||||
assertThat(testedContentTypes).containsExactly(TEST_CONTENT_TYPE);
|
assertThat(testedContentTypes).hasSize(1);
|
||||||
|
assertThat(testedContentTypes.get(0)).isEqualTo(TEST_CONTENT_TYPE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1289,7 +1277,7 @@ public final class HttpEngineDataSourceTest {
|
|||||||
.createDataSource();
|
.createDataSource();
|
||||||
mockSingleRedirectSuccess(/* responseCode= */ 302);
|
mockSingleRedirectSuccess(/* responseCode= */ 302);
|
||||||
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
|
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
|
||||||
testResponseHeader.put("Set-Cookie", TEST_RESPONSE_SET_COOKIE);
|
testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video");
|
||||||
|
|
||||||
dataSourceUnderTest.open(testPostDataSpec);
|
dataSourceUnderTest.open(testPostDataSpec);
|
||||||
|
|
||||||
@ -1461,52 +1449,6 @@ public final class HttpEngineDataSourceTest {
|
|||||||
verify(mockUrlRequestBuilder).setDirectExecutorAllowed(true);
|
verify(mockUrlRequestBuilder).setDirectExecutorAllowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getCookieHeader_noCookieHandler() {
|
|
||||||
assertThat(HttpEngineDataSource.UrlRequestCallback.getCookieHeader(TEST_URL)).isEmpty();
|
|
||||||
assertThat(CookieHandler.getDefault()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getCookieHeader_emptyCookieHandler() {
|
|
||||||
CookieHandler.setDefault(new CookieManager());
|
|
||||||
assertThat(HttpEngineDataSource.UrlRequestCallback.getCookieHeader(TEST_URL)).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getCookieHeader_cookieHandler() throws Exception {
|
|
||||||
CookieManager cm = new CookieManager();
|
|
||||||
cm.put(
|
|
||||||
new URI(TEST_URL),
|
|
||||||
ImmutableMap.of(
|
|
||||||
"Set-Cookie", ImmutableList.of(TEST_RESPONSE_SET_COOKIE, TEST_RESPONSE_SET_COOKIE_2)));
|
|
||||||
CookieHandler.setDefault(cm);
|
|
||||||
|
|
||||||
assertThat(HttpEngineDataSource.UrlRequestCallback.getCookieHeader(TEST_URL))
|
|
||||||
.isEqualTo(TEST_REQUEST_COOKIE + "; " + TEST_REQUEST_COOKIE_2 + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getCookieHeader_cookieHandlerCookie2() throws Exception {
|
|
||||||
CookieManager cm = new CookieManager();
|
|
||||||
cm.put(
|
|
||||||
new URI(TEST_URL),
|
|
||||||
ImmutableMap.of(
|
|
||||||
"Set-Cookie2", ImmutableList.of(TEST_RESPONSE_SET_COOKIE, TEST_RESPONSE_SET_COOKIE_2)));
|
|
||||||
CookieHandler.setDefault(cm);
|
|
||||||
|
|
||||||
// This asserts the surprising behavior of CookieManager - Set-Cookie2 is translated to Cookie,
|
|
||||||
// not Cookie2.
|
|
||||||
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of()))).isNotEmpty();
|
|
||||||
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of())).get("Cookie"))
|
|
||||||
.containsExactly(TEST_REQUEST_COOKIE, TEST_REQUEST_COOKIE_2);
|
|
||||||
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of())))
|
|
||||||
.doesNotContainKey("Cookie2");
|
|
||||||
|
|
||||||
assertThat(HttpEngineDataSource.UrlRequestCallback.getCookieHeader(TEST_URL))
|
|
||||||
.isEqualTo(TEST_REQUEST_COOKIE + "; " + TEST_REQUEST_COOKIE_2 + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper methods.
|
// Helper methods.
|
||||||
|
|
||||||
private void mockStatusResponse() {
|
private void mockStatusResponse() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user