blob: d4ff9681f885b17fa9421a2244754ac957fccefe [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.providers.media.util;
import static android.content.ContentResolver.QUERY_ARG_GROUP_COLUMNS;
import static android.content.ContentResolver.QUERY_ARG_LIMIT;
import static android.content.ContentResolver.QUERY_ARG_OFFSET;
import static android.content.ContentResolver.QUERY_ARG_SORT_COLLATION;
import static android.content.ContentResolver.QUERY_ARG_SORT_COLUMNS;
import static android.content.ContentResolver.QUERY_ARG_SORT_DIRECTION;
import static android.content.ContentResolver.QUERY_ARG_SORT_LOCALE;
import static android.content.ContentResolver.QUERY_ARG_SQL_GROUP_BY;
import static android.content.ContentResolver.QUERY_ARG_SQL_LIMIT;
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
import static android.content.ContentResolver.QUERY_SORT_DIRECTION_ASCENDING;
import static android.database.DatabaseUtils.bindSelection;
import static android.database.DatabaseUtils.escapeForLike;
import static com.android.providers.media.util.DatabaseUtils.maybeBalance;
import static com.android.providers.media.util.DatabaseUtils.parseBoolean;
import static com.android.providers.media.util.DatabaseUtils.recoverAbusiveLimit;
import static com.android.providers.media.util.DatabaseUtils.recoverAbusiveSortOrder;
import static com.android.providers.media.util.DatabaseUtils.resolveQueryArgs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.ArraySet;
import android.util.Pair;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.function.Function;
@RunWith(AndroidJUnit4.class)
public class DatabaseUtilsTest {
private final Bundle args = new Bundle();
private final ArraySet<String> honored = new ArraySet<>();
private static final Object[] ARGS = { "baz", 4, null, (double) 3.14159, false };
@Before
public void setUp() {
args.clear();
honored.clear();
}
@Test
public void testConstructor() {
new DatabaseUtils();
}
@Test
public void testBindSelection_none() throws Exception {
assertEquals(null,
bindSelection(null, ARGS));
assertEquals("",
bindSelection("", ARGS));
assertEquals("foo=bar",
bindSelection("foo=bar", ARGS));
}
@Test
public void testBindSelection_normal() throws Exception {
assertEquals("foo='baz'",
bindSelection("foo=?", ARGS));
assertEquals("foo='baz' AND bar=4",
bindSelection("foo=? AND bar=?", ARGS));
assertEquals("foo='baz' AND bar=4 AND meow=NULL",
bindSelection("foo=? AND bar=? AND meow=?", ARGS));
}
@Test
public void testBindSelection_whitespace() throws Exception {
assertEquals("BETWEEN 5 AND 10",
bindSelection("BETWEEN? AND ?", 5, 10));
assertEquals("IN 'foo'",
bindSelection("IN?", "foo"));
}
@Test
public void testBindSelection_indexed() throws Exception {
assertEquals("foo=10 AND bar=11 AND meow=1",
bindSelection("foo=?10 AND bar=? AND meow=?1",
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
}
@Test
public void testBindSelection_types() throws Exception {
assertEquals("'baz'", bindSelection("?1", ARGS));
assertEquals("4", bindSelection("?2", ARGS));
assertEquals("NULL", bindSelection("?3", ARGS));
assertEquals("3.14159", bindSelection("?4", ARGS));
assertEquals("0", bindSelection("?5", ARGS));
}
@Test
public void testResolveQueryArgs_GroupBy() throws Exception {
args.putStringArray(QUERY_ARG_GROUP_COLUMNS, new String[] { "foo", "bar" });
args.putString(QUERY_ARG_SQL_GROUP_BY, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_GROUP_COLUMNS));
assertFalse(honored.contains(QUERY_ARG_SQL_GROUP_BY));
assertEquals("foo, bar", args.getString(QUERY_ARG_SQL_GROUP_BY));
}
@Test
public void testResolveQueryArgs_GroupBy_Raw() throws Exception {
args.putString(QUERY_ARG_SQL_GROUP_BY, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_SQL_GROUP_BY));
assertEquals("raw", args.getString(QUERY_ARG_SQL_GROUP_BY));
}
@Test
public void testResolveQueryArgs_SortOrder_Simple() throws Exception {
args.putStringArray(QUERY_ARG_SORT_COLUMNS, new String[] { "foo", "bar" });
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_SORT_COLUMNS));
assertFalse(honored.contains(QUERY_ARG_SQL_SORT_ORDER));
assertEquals("foo, bar", args.getString(QUERY_ARG_SQL_SORT_ORDER));
}
@Test
public void testResolveQueryArgs_SortOrder_Locale() throws Exception {
args.putStringArray(QUERY_ARG_SORT_COLUMNS, new String[] { "foo", "bar" });
args.putString(QUERY_ARG_SORT_LOCALE, "zh");
args.putInt(QUERY_ARG_SORT_DIRECTION, QUERY_SORT_DIRECTION_ASCENDING);
args.putInt(QUERY_ARG_SORT_COLLATION, java.text.Collator.IDENTICAL);
args.putString(QUERY_ARG_SQL_SORT_ORDER, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_SORT_COLUMNS));
assertTrue(honored.contains(QUERY_ARG_SORT_LOCALE));
assertTrue(honored.contains(QUERY_ARG_SORT_DIRECTION));
assertFalse(honored.contains(QUERY_ARG_SORT_COLLATION));
assertFalse(honored.contains(QUERY_ARG_SQL_SORT_ORDER));
assertEquals("foo, bar COLLATE zh ASC", args.getString(QUERY_ARG_SQL_SORT_ORDER));
}
@Test
public void testResolveQueryArgs_SortOrder_Raw() throws Exception {
args.putString(QUERY_ARG_SQL_SORT_ORDER, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_SQL_SORT_ORDER));
assertEquals("raw", args.getString(QUERY_ARG_SQL_SORT_ORDER));
}
@Test
public void testResolveQueryArgs_Limit() throws Exception {
args.putInt(QUERY_ARG_LIMIT, 32);
args.putInt(QUERY_ARG_OFFSET, 64);
args.putString(QUERY_ARG_SQL_LIMIT, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_LIMIT));
assertTrue(honored.contains(QUERY_ARG_OFFSET));
assertFalse(honored.contains(QUERY_ARG_SQL_LIMIT));
assertEquals("32 OFFSET 64", args.getString(QUERY_ARG_SQL_LIMIT));
}
@Test
public void testResolveQueryArgs_Limit_Raw() throws Exception {
args.putString(QUERY_ARG_SQL_LIMIT, "raw");
resolveQueryArgs(args, honored::add, Function.identity());
assertTrue(honored.contains(QUERY_ARG_SQL_LIMIT));
assertEquals("raw", args.getString(QUERY_ARG_SQL_LIMIT));
}
@Test
public void testRecoverAbusiveLimit_Uri() throws Exception {
final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
.appendQueryParameter("limit", "32").build();
recoverAbusiveLimit(uri, args);
assertEquals("32", args.getString(QUERY_ARG_SQL_LIMIT));
}
@Test
public void testRecoverAbusiveLimit_Args() throws Exception {
final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
args.putString(QUERY_ARG_SQL_LIMIT, "32");
recoverAbusiveLimit(uri, args);
assertEquals("32", args.getString(QUERY_ARG_SQL_LIMIT));
}
@Test
public void testRecoverAbusiveLimit_Both() throws Exception {
final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
.appendQueryParameter("limit", "32").build();
args.putString(QUERY_ARG_SQL_LIMIT, "32");
try {
recoverAbusiveLimit(uri, args);
fail("Expected IAE when conflicting limits defined");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testRecoverAbusiveGroupBy_Conflicting() throws Exception {
// Abusive group is fine
recoverAbusiveGroupBy(Pair.create("foo=bar GROUP BY foo", null));
// Official group is fine
recoverAbusiveGroupBy(Pair.create("foo=bar", "foo"));
// Conflicting groups should yell
try {
recoverAbusiveGroupBy(Pair.create("foo=bar GROUP BY foo", "foo"));
fail("Expected IAE when conflicting groups defined");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testRecoverAbusiveGroupBy_Buckets() throws Exception {
final Pair<String, String> input = Pair.create(
"(media_type = 1 OR media_type = 3) AND bucket_display_name IS NOT NULL AND bucket_id IS NOT NULL AND _data NOT LIKE \"%/DCIM/%\" ) GROUP BY (bucket_id",
null);
final Pair<String, String> expected = Pair.create(
"((media_type = 1 OR media_type = 3) AND bucket_display_name IS NOT NULL AND bucket_id IS NOT NULL AND _data NOT LIKE \"%/DCIM/%\" )",
"(bucket_id)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_BucketsByPath() throws Exception {
final Pair<String, String> input = Pair.create(
"_data LIKE ? AND _data IS NOT NULL) GROUP BY (bucket_id",
null);
final Pair<String, String> expected = Pair.create(
"(_data LIKE ? AND _data IS NOT NULL)",
"(bucket_id)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_113651872() throws Exception {
final Pair<String, String> input = Pair.create(
"(LOWER(SUBSTR(_data, -4))=? OR LOWER(SUBSTR(_data, -5))=? OR LOWER(SUBSTR(_data, -4))=?) AND LOWER(SUBSTR(_data, 1, 65))!=?) GROUP BY (bucket_id),(bucket_display_name",
null);
final Pair<String, String> expected = Pair.create(
"((LOWER(SUBSTR(_data, -4))=? OR LOWER(SUBSTR(_data, -5))=? OR LOWER(SUBSTR(_data, -4))=?) AND LOWER(SUBSTR(_data, 1, 65))!=?)",
"(bucket_id),(bucket_display_name)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_113652519() throws Exception {
final Pair<String, String> input = Pair.create(
"1) GROUP BY 1,(2",
null);
final Pair<String, String> expected = Pair.create(
"(1)",
"1,(2)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_113652519_longer() throws Exception {
final Pair<String, String> input = Pair.create(
"mime_type IN ( ?, ?, ? ) AND 1) GROUP BY 1,(2",
null);
final Pair<String, String> expected = Pair.create(
"(mime_type IN ( ?, ?, ? ) AND 1)",
"1,(2)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_115340326() throws Exception {
final Pair<String, String> input = Pair.create(
"(1) GROUP BY bucket_id,(bucket_display_name)",
null);
final Pair<String, String> expected = Pair.create(
"(1)",
"bucket_id,(bucket_display_name)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveGroupBy_116845885() throws Exception {
final Pair<String, String> input = Pair.create(
"(title like 'C360%' or title like 'getInstance%') group by ((datetaken+19800000)/86400000)",
null);
final Pair<String, String> expected = Pair.create(
"(title like 'C360%' or title like 'getInstance%')",
"((datetaken+19800000)/86400000)");
assertEquals(expected, recoverAbusiveGroupBy(input));
}
@Test
public void testRecoverAbusiveSortOrder_146482076() throws Exception {
args.putString(QUERY_ARG_SQL_SORT_ORDER, "_id DESC LIMIT 200");
recoverAbusiveSortOrder(args);
assertEquals("_id DESC", args.getString(QUERY_ARG_SQL_SORT_ORDER));
assertEquals("200", args.getString(QUERY_ARG_SQL_LIMIT));
}
@Test
public void testMaybeBalance() throws Exception {
assertEquals(null, maybeBalance(null));
assertEquals("", maybeBalance(""));
assertEquals("()", maybeBalance(")"));
assertEquals("()", maybeBalance("("));
assertEquals("()", maybeBalance("()"));
assertEquals("(1==1)", maybeBalance("1==1)"));
assertEquals("((foo)bar)baz", maybeBalance("foo)bar)baz"));
assertEquals("foo(bar(baz))", maybeBalance("foo(bar(baz"));
assertEquals("IN '('", maybeBalance("IN '('"));
assertEquals("IN ('(')", maybeBalance("IN ('('"));
assertEquals("IN (\")\")", maybeBalance("IN (\")\""));
assertEquals("IN ('\"(')", maybeBalance("IN ('\"('"));
}
@Test
public void testBindList() {
assertEquals("()", DatabaseUtils.bindList());
assertEquals("( 'foo' )", DatabaseUtils.bindList("foo"));
assertEquals("( 'foo' , 'bar' )", DatabaseUtils.bindList("foo", "bar"));
assertEquals("( 'foo' , 'bar' , 'baz' )", DatabaseUtils.bindList("foo", "bar", "baz"));
assertEquals("( 'foo' , NULL , 42 )", DatabaseUtils.bindList("foo", null, 42));
}
@Test
public void testEscapeForLike() throws Exception {
assertEquals("file.bin",
escapeForLike("file.bin"));
assertEquals("/path/to/file.bin",
escapeForLike("/path/to/file.bin"));
assertEquals("/path/to/fi\\_le.bin",
escapeForLike("/path/to/fi_le.bin"));
assertEquals("/path/to/fi\\%le.bin",
escapeForLike("/path/to/fi%le.bin"));
}
@Test
public void testParseBoolean() throws Exception {
assertTrue(parseBoolean("TRUE", false));
assertTrue(parseBoolean("true", false));
assertTrue(parseBoolean("1", false));
assertTrue(parseBoolean(1, false));
assertTrue(parseBoolean(true, false));
assertFalse(parseBoolean("FALSE", true));
assertFalse(parseBoolean("false", true));
assertFalse(parseBoolean("0", true));
assertFalse(parseBoolean(0, true));
assertFalse(parseBoolean(false, true));
assertFalse(parseBoolean(null, false));
assertTrue(parseBoolean(null, true));
}
private static Pair<String, String> recoverAbusiveGroupBy(
Pair<String, String> selectionAndGroupBy) {
final Bundle queryArgs = new Bundle();
queryArgs.putString(QUERY_ARG_SQL_SELECTION, selectionAndGroupBy.first);
queryArgs.putString(QUERY_ARG_SQL_GROUP_BY, selectionAndGroupBy.second);
DatabaseUtils.recoverAbusiveSelection(queryArgs);
return Pair.create(queryArgs.getString(QUERY_ARG_SQL_SELECTION),
queryArgs.getString(QUERY_ARG_SQL_GROUP_BY));
}
}