Remove null keys from DefaultHttpDataSource#getResponseHeaders

PiperOrigin-RevId: 410507648
This commit is contained in:
ibaker 2021-11-17 14:12:37 +00:00 committed by Ian Baker
parent d5b6250350
commit 191185096c
3 changed files with 76 additions and 12 deletions

View File

@ -20,7 +20,6 @@ import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv; import androidx.media3.test.utils.HttpDataSourceTestEnv;
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.ImmutableList;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -44,8 +43,4 @@ public class DefaultHttpDataSourceContractTest extends DataSourceContractTest {
protected Uri getNotFoundUri() { protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl()); return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
} }
@Override
@Ignore("internal b/205811776")
public void getResponseHeaders_noNullKeysOrValues() {}
} }

View File

@ -30,6 +30,9 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSpec.HttpMethod; import androidx.media3.datasource.DataSpec.HttpMethod;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -40,10 +43,10 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.NoRouteToHostException; import java.net.NoRouteToHostException;
import java.net.URL; import java.net.URL;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@ -312,7 +315,18 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Override @Override
public Map<String, List<String>> getResponseHeaders() { public Map<String, List<String>> getResponseHeaders() {
return connection == null ? Collections.emptyMap() : connection.getHeaderFields(); if (connection == null) {
return ImmutableMap.of();
}
// connection.getHeaderFields() always contains a null key with a value like
// ["HTTP/1.1 200 OK"]. The response code is available from HttpURLConnection#getResponseCode()
// and the HTTP version is fixed when establishing the connection.
// DataSource#getResponseHeaders() doesn't allow null keys in the returned map, so we need to
// remove it.
// connection.getHeaderFields() returns a special unmodifiable case-insensitive Map
// so we can't just remove the null key or make a copy without the null key. Instead we wrap it
// in a ForwardingMap subclass that ignores and filters out null keys in the read methods.
return new NullFilteringHeadersMap(connection.getHeaderFields());
} }
@Override @Override
@ -817,4 +831,64 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
String contentEncoding = connection.getHeaderField("Content-Encoding"); String contentEncoding = connection.getHeaderField("Content-Encoding");
return "gzip".equalsIgnoreCase(contentEncoding); return "gzip".equalsIgnoreCase(contentEncoding);
} }
private static class NullFilteringHeadersMap extends ForwardingMap<String, List<String>> {
private final Map<String, List<String>> headers;
public NullFilteringHeadersMap(Map<String, List<String>> headers) {
this.headers = headers;
}
@Override
protected Map<String, List<String>> delegate() {
return headers;
}
@Override
public boolean containsKey(@Nullable Object key) {
return key != null && super.containsKey(key);
}
@Nullable
@Override
public List<String> get(@Nullable Object key) {
return key == null ? null : super.get(key);
}
@Override
public Set<String> keySet() {
return Sets.filter(super.keySet(), key -> key != null);
}
@Override
public Set<Entry<String, List<String>>> entrySet() {
return Sets.filter(super.entrySet(), entry -> entry.getKey() != null);
}
@Override
public int size() {
return super.size() - (super.containsKey(null) ? 1 : 0);
}
@Override
public boolean isEmpty() {
return super.isEmpty() || (super.size() == 1 && super.containsKey(null));
}
@Override
public boolean containsValue(@Nullable Object value) {
return super.standardContainsValue(value);
}
@Override
public boolean equals(@Nullable Object object) {
return object != null && super.standardEquals(object);
}
@Override
public int hashCode() {
return super.standardHashCode();
}
}
} }

View File

@ -547,11 +547,6 @@ public abstract class DataSourceContractTest {
Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders(); Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders();
for (String key : responseHeaders.keySet()) { for (String key : responseHeaders.keySet()) {
// TODO(internal b/205811776): Remove this when DefaultHttpDataSource is fixed to not
// return a null key.
if (key == null) {
continue;
}
String caseFlippedKey = invertAsciiCaseOfEveryOtherCharacter(key); String caseFlippedKey = invertAsciiCaseOfEveryOtherCharacter(key);
assertWithMessage("key='%s', caseFlippedKey='%s'", key, caseFlippedKey) assertWithMessage("key='%s', caseFlippedKey='%s'", key, caseFlippedKey)
.that(responseHeaders.get(caseFlippedKey)) .that(responseHeaders.get(caseFlippedKey))