Add DataSource contract test checking scheme case insensitivity

Systems accepting URIs should treat schemes as case-insensitive
([RFC 3986 Section 3.1](https://www.rfc-editor.org/rfc/rfc3986#section-3.1)):
>  An implementation should accept uppercase letters as equivalent to
>  lowercase in scheme names (e.g., allow "HTTP" as well as "http") for
>  the sake of robustness

PiperOrigin-RevId: 528735287
This commit is contained in:
ibaker 2023-05-02 11:47:27 +01:00 committed by Marc Baechinger
parent ad2d4f5008
commit b4b7e0e7c0
8 changed files with 65 additions and 14 deletions

View File

@ -124,6 +124,8 @@
* Text: * Text:
* SSA: Add support for UTF-16 files if they start with a byte order mark * SSA: Add support for UTF-16 files if they start with a byte order mark
([#319](https://github.com/androidx/media/issues/319)). ([#319](https://github.com/androidx/media/issues/319)).
* Test Utilities:
* Check for URI scheme case insensitivity in `DataSourceContractTest`.
* Remove deprecated symbols: * Remove deprecated symbols:
* Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder` * Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder`
instead. instead.

View File

@ -75,13 +75,13 @@ public final class ContentDataSource extends BaseDataSource {
@SuppressWarnings("InlinedApi") // We are inlining EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT. @SuppressWarnings("InlinedApi") // We are inlining EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT.
public long open(DataSpec dataSpec) throws ContentDataSourceException { public long open(DataSpec dataSpec) throws ContentDataSourceException {
try { try {
Uri uri = dataSpec.uri; Uri uri = dataSpec.uri.normalizeScheme();
this.uri = uri; this.uri = uri;
transferInitializing(dataSpec); transferInitializing(dataSpec);
AssetFileDescriptor assetFileDescriptor; AssetFileDescriptor assetFileDescriptor;
if ("content".equals(dataSpec.uri.getScheme())) { if ("content".equals(uri.getScheme())) {
Bundle providerOptions = new Bundle(); Bundle providerOptions = new Bundle();
// We don't want compatible media transcoding. // We don't want compatible media transcoding.
providerOptions.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true); providerOptions.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);

View File

@ -50,7 +50,7 @@ public final class DataSchemeDataSource extends BaseDataSource {
public long open(DataSpec dataSpec) throws IOException { public long open(DataSpec dataSpec) throws IOException {
transferInitializing(dataSpec); transferInitializing(dataSpec);
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
Uri uri = dataSpec.uri; Uri uri = dataSpec.uri.normalizeScheme();
String scheme = uri.getScheme(); String scheme = uri.getScheme();
Assertions.checkArgument(SCHEME_DATA.equals(scheme), "Unsupported scheme: " + scheme); Assertions.checkArgument(SCHEME_DATA.equals(scheme), "Unsupported scheme: " + scheme);
String[] uriParts = Util.split(uri.getSchemeSpecificPart(), ","); String[] uriParts = Util.split(uri.getSchemeSpecificPart(), ",");

View File

@ -116,7 +116,7 @@ public final class RawResourceDataSource extends BaseDataSource {
@Override @Override
public long open(DataSpec dataSpec) throws RawResourceDataSourceException { public long open(DataSpec dataSpec) throws RawResourceDataSourceException {
Uri uri = dataSpec.uri; Uri uri = dataSpec.uri.normalizeScheme();
this.uri = uri; this.uri = uri;
int resourceId; int resourceId;
@ -150,10 +150,13 @@ public final class RawResourceDataSource extends BaseDataSource {
} }
} else { } else {
throw new RawResourceDataSourceException( throw new RawResourceDataSourceException(
"URI must either use scheme " "Unsupported URI scheme ("
+ uri.getScheme()
+ "). Only "
+ RAW_RESOURCE_SCHEME + RAW_RESOURCE_SCHEME
+ " or " + " and "
+ ContentResolver.SCHEME_ANDROID_RESOURCE, + ContentResolver.SCHEME_ANDROID_RESOURCE
+ " are supported.",
/* cause= */ null, /* cause= */ null,
PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK); PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK);
} }

View File

@ -68,7 +68,7 @@ public class ResolvingDataSourceContractTest extends DataSourceContractTest {
new Resolver() { new Resolver() {
@Override @Override
public DataSpec resolveDataSpec(DataSpec dataSpec) throws IOException { public DataSpec resolveDataSpec(DataSpec dataSpec) throws IOException {
return URI.equals(dataSpec.uri.toString()) return URI.equals(dataSpec.uri.normalizeScheme().toString())
? dataSpec.buildUpon().setUri(RESOLVED_URI).build() ? dataSpec.buildUpon().setUri(RESOLVED_URI).build()
: dataSpec; : dataSpec;
} }

View File

@ -386,6 +386,44 @@ public abstract class DataSourceContractTest {
} }
} }
@Test
public void uriSchemeIsCaseInsensitive() throws Exception {
ImmutableList<TestResource> resources = getTestResources();
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource.");
for (int i = 0; i < resources.size(); i++) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i));
TestResource resource = resources.get(i);
@Nullable String scheme = resource.getUri().getScheme();
if (scheme == null) {
// No scheme for which to check case-insensitivity.
continue;
}
DataSource dataSource = createDataSource();
Uri uri =
resource
.getUri()
.buildUpon()
.scheme(invertAsciiCaseOfEveryOtherCharacter(scheme))
.build();
try {
long length = dataSource.open(new DataSpec.Builder().setUri(uri).build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(resource.getExpectedBytes().length);
}
assertThat(data).isEqualTo(resource.getExpectedBytes());
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
}
@Test @Test
public void resourceNotFound() throws Exception { public void resourceNotFound() throws Exception {
DataSource dataSource = createDataSource(); DataSource dataSource = createDataSource();

View File

@ -267,7 +267,7 @@ public class FakeDataSet {
/** Returns a new {@link FakeData} with the given {@code uri}. */ /** Returns a new {@link FakeData} with the given {@code uri}. */
public FakeData newData(Uri uri) { public FakeData newData(Uri uri) {
FakeData data = new FakeData(this, uri); FakeData data = new FakeData(this, uri);
dataMap.put(uri, data); dataMap.put(uri.normalizeScheme(), data);
return data; return data;
} }
@ -280,7 +280,7 @@ public class FakeDataSet {
/** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */ /** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */
@Nullable @Nullable
public FakeData getData(Uri uri) { public FakeData getData(Uri uri) {
@Nullable FakeData data = dataMap.get(uri); @Nullable FakeData data = dataMap.get(uri.normalizeScheme());
return data != null ? data : defaultData; return data != null ? data : defaultData;
} }

View File

@ -51,11 +51,11 @@ public final class FakeDataSetTest {
.setData(uris[2], testData[3]); .setData(uris[2], testData[3]);
assertThat(fakeDataSet.getAllData().size()).isEqualTo(4); assertThat(fakeDataSet.getAllData().size()).isEqualTo(4);
assertThat(fakeDataSet.getData("unseen_uri")).isEqualTo(fakeDataSet.getData((Uri) null)); assertThat(fakeDataSet.getData("unseen_uri")).isEqualTo(fakeDataSet.getData("not a real key"));
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
assertThat(fakeDataSet.getData(uris[i]).uri).isEqualTo(uris[i]); assertThat(fakeDataSet.getData(uris[i]).uri).isEqualTo(uris[i]);
} }
assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(testData[0]); assertThat(fakeDataSet.getData("not a real key").getData()).isEqualTo(testData[0]);
for (int i = 1; i < 4; i++) { for (int i = 1; i < 4; i++) {
assertThat(fakeDataSet.getData(uris[i - 1]).getData()).isEqualTo(testData[i]); assertThat(fakeDataSet.getData(uris[i - 1]).getData()).isEqualTo(testData[i]);
} }
@ -79,7 +79,7 @@ public final class FakeDataSetTest {
.appendReadError(exception) .appendReadError(exception)
.endData(); .endData();
List<Segment> segments = fakeDataSet.getData((Uri) null).getSegments(); List<Segment> segments = fakeDataSet.getData("not a real key").getSegments();
assertThat(segments.size()).isEqualTo(5); assertThat(segments.size()).isEqualTo(5);
assertSegment(segments.get(0), testData, 3, 0, null, null); assertSegment(segments.get(0), testData, 3, 0, null, null);
assertSegment(segments.get(1), testData, 3, 3, null, null); assertSegment(segments.get(1), testData, 3, 3, null, null);
@ -90,7 +90,15 @@ public final class FakeDataSetTest {
byte[] allData = new byte[6]; byte[] allData = new byte[6];
System.arraycopy(testData, 0, allData, 0, 3); System.arraycopy(testData, 0, allData, 0, 3);
System.arraycopy(testData, 0, allData, 3, 3); System.arraycopy(testData, 0, allData, 3, 3);
assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(allData); assertThat(fakeDataSet.getData("not a real key").getData()).isEqualTo(allData);
}
@Test
public void uriSchemesAreCaseInsensitive() {
byte[] data = TestUtil.buildTestData(3);
FakeDataSet fakeDataSet = new FakeDataSet().setData("HtTp://example.test/path/to/data", data);
assertThat(fakeDataSet.getData("hTtP://example.test/path/to/data").getData()).isEqualTo(data);
} }
private static void assertSegment( private static void assertSegment(