Android SearchRecentSuggestionsProvider query limit - android

I'm working with search suggestion framework and here is the case. I get some query string and make query from content resolver, but problem is that I can't limit the results. Found several solutions but they dont work for me.
My content provider is extending SearchRecentSuggestionsProvider and declared in manifest, here query uri
Uri URI = Uri.parse("content://" + AUTHORITY + "/search_suggest_query");
sol 1:
adding query parameter to uri
SearchSuggestionProvider.URI.buildUpon().appendQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT, String.valueOf(5)).build()
sol 2:
adding limit in sortOrder query parameter
getContentResolver().query(URI, null, "?", new String[]{searchQuery}, "_id asc limit 5");
In both cases query returns all rows from search suggestions. Does anyone know how to limit such a query?

Actually, we were searching in the wrong place.
After all this investigations, finally I found the trick.
On the results screen (Where we save the query to the search SearchRecentSuggestions) we call this method
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE);
suggestions.saveRecentQuery(query, null);
SearchRecentSuggestions was the responsible to save this query to the content provider.
This class has a protected method called truncateHistory() with this documentation
Reduces the length of the history table, to prevent it from growing
too large.
So the solution is simply, create a custom class, override this method, and call the super implementation with the required limit.
Here is the example
public class SearchRecentSuggestionsLimited extends SearchRecentSuggestions {
private int limit;
public SearchRecentSuggestionsLimited(Context context, String authority, int mode, int limit) {
super(context, authority, mode);
this.limit = limit;
}
#Override
protected void truncateHistory(ContentResolver cr, int maxEntries) {
super.truncateHistory(cr, limit);
}
}

Below is the query method in the base class SearchRecentSuggestionsProvider in the framework
/**
* This method is provided for use by the ContentResolver. Do not override, or directly
* call from your own code.
*/
// TODO: Confirm no injection attacks here, or rewrite.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Potentially you can override it and implement the limiting - but as you can see the comment above it clear states "do not override".
In the existing method no limiting is enforced.
However I see in some of the other answers on stack-overflow and other sites that people do override it.
Example :
Use SearchRecentSuggestionsProvider with some predefined terms?
Based on this I think you can try it out - override the query method and add your own implementation which supports limiting the results. Parse the SearchManager.SUGGEST_PARAMETER_LIMIT that you have used in the query method and use it to limit the results returned.

Related

How to sanitize all variables passed to the selectionArgs array?

Veracode Static Scan report points SQL Injection flaw in my Content Provider implementation.
Previously, I posted this question related to all my doubts regarding this flaw.
And after few discussions I came to a conclusion that there might be chances of it being a false positive in the report. Because according to what I researched and read, I was following the security guidelines mentioned in Android docs and other referenced sources to avoid SQL Injection.
There is suggestion everywhere to perform at least some input validation on the data passed to SQL queries.I want to cover this possibility which be the reason of flaw.
Everyone is asking me to sanitize data before passing to query.
How do I exactly sanitize variables passed to selectionArgs array passed to delete(), update() method of Content Provider?
Will DatabaseUtils.sqlEscapeString() be sufficient?
Please suggest!
Here's the implementation where I need to sanitize the variable:
public Loader<Cursor> onCreateLoader(int id, Bundle b) {
switch (id) {
case THOUGHT_LOADER:
return new CursorLoader(getActivity(), NewsFeedTable.CONTENT_URI, NewsFeedTable.PROJECTION, NewsFeedTable._id + "=?", new String[]{tid}, null);
case COMMENT_LOADER:
return new CursorLoader(getActivity(), CommentTable.CONTENT_URI, CommentTable.PROJECTION, CommentTable.COLUMN_TID + "=?", new String[]{tid}, null);
default:
return null;
}
}
Report points to the flaw :Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWEID 89) at this line
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs); in the below code:
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri.equals(Contract.BASE_CONTENT_URI)) {
deleteDatabase();
return 1;
}
SQLiteDatabase db = openHelper.getWritableDatabase();
int deleted = 0;
switch (matcher.match(uri)) {
case BULLETIN:
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs);
break;
case CLASSROOMS:
deleted = db.delete(ClassroomsTable.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}
Values in the selectionArgs array never need to be sanitized, because they cannot be interpreted as SQL commands (that's the whole point of having separate parameter values).
The purpose of sqlEscapeString() is to format a string so that it can be put into an SQL command (i.e., escape single quotes; all other characters have no meaning inside an SQL string). But when you know that there is a string, you should selectionArgs instead, so this function is not helpful.
You need to sanitize only strings that end up in the SQL command itself. In this case, this would be selection. If this value comes from the user, or from some other app, then you have no control over how much stuff your DELETE statement actually does (it could call SQL functions, or execute subqueries that access other parts of the database).
For practical purposes, it is not possible to sanitize strings that are intended to contain SQL commands, because then you would need a full SQL parser. If your content provider is available for external code, you should allow deleting specific items only through the URI, and disallow custom selections.

Save to database from multiple Fragments

In my Android app i have an Activity with tabs and each tab changes to a different Fragment, each with several fields the user should fill. The user can then submit all data by clicking a button in the action bar.
Is there an easy way to access and store all data from the different Fragments at action bar button click? Am i missing something here?
If I understood your problem right, I'd see two possible solutions:
1) To copy the data from the fragments to a class extending the Application class, so that class work as a placeholder for global variables. You only would need to capture the input events on the fragments to update the class, or to capture the fragment transition to copy the data from it to the Application
2) You can inflate your fragments' views into the main activity one by setting the attachToRoot flag. Then it should be possible to find them with findViewById in the main activity
It is unclear on what you mean by save. If you want to store the data locally on the phone:
Android Storage options
From these I strongly recommend SQLite for more complex data and Shared Preferences for simple (String or int) type data.
When You Entering the data in First Fragment that all data you can store it into one String by comma separated value and then access that values into second fragment and split that string and store it into Sqlite DB .
If I understand correctly you want to save data to your database from various parts of your app. I would suggest you use ContentProvider/ContentResolver for this. With this approach you would implement a ContentProvider which interacts with your database and from anywhere else in the app you would use a ContentResolver to interact with the ContentProvider.
In your ContentProvider you would have to implement the methods, query(), insert(), delete(), update(), onCreate() and getType(). Uris are used to identify what you want to insert or select from the ContentProvider. You can use a static UriMatcher to make parsing of the Uris very simple. I will give you an example on how to implement the query method with a sqlite database:
This is how you would define an UriMatcher in your Provider:
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
// Here you define your Uris, in this case for a table I called TABLE_ONE.
// If you want to know what each of these parameters means I suggest you view the documentation
sURIMatcher.addURI(AUTHORITY, BASE_PATH, TABLE_ONE_ID);
}
// I suggest you define all constants like the AUTHORITY and BASEPATH and Uri's in a Contract class.
// You create the Uris from the value of AUTHORITY and BASE_PATH like this:
public static final Uri TABLE_ONE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
And this is a rudimentary implementation of the query method:
public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor c = null;
// The UriMatcher gives you the id of the uri if you added it like in the above example
int uriId = sURIMatcher.match(uri);
switch (uriId ) {
case TABLE_ONE_ID:
SQLiteDatabase db = database.getWritableDatabase();
c = db.query(TABLE_ONE_NAME, projecton, selection, selectionArgs, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return c;
}
And then in your app you can select data from the db using a ContentResolver like this:
// Note that getContentResolver is a method of Activity, in a Fragment you have to call getActivity().getContentResolver()
getContentResolver().query(TABLE_ONE_URI, projection, selection, selectionArgs, sortOrder);
Here is the link to a complete Tutorial about pretty much anything important regarding SQLite and ContentProviders in Android:
http://www.vogella.com/articles/AndroidSQLite/article.html
Here you can find the official guide from Google on how to implement a ContentProvider:
http://developer.android.com/guide/topics/providers/content-provider-creating.html

How to add limit clause using content provider [duplicate]

This question already has answers here:
Specifying limit / offset for ContentProvider queries
(4 answers)
Closed 4 years ago.
Is there a way to limit the number of rows returned from content provider?
I found this solution, however, it did not work for me. All of the rows are still being returned.
Uri uri = Playlists.createIdUri(playlistId); //generates URI
uri = uri.buildUpon().appendQueryParameter("limit", "3").build();
Cursor cursor = activity.managedQuery(playlistUri, null, null, null, null);
I have had this issue and had to break my head till I finally figured it out, or rather got a whay that worked for me. Try the following
Cursor cursor = activity.managedQuery(playlistUri, null, null, null, " ASC "+" LIMIT 2");
The last parameter is for sortOrder. I provided the sort order and also appended the LIMIT to it. Make sure you give the spaces properly. I had to check the query that was being formed and this seemed to work.
Unfortunately, ContentResolver can't query having limit argument. Inside your ContentProvider, your MySQLQueryBuilder can query adding the additional limit parameter.
Following the agenda, we can add an additional URI rule inside ContentProvider.
static final int ELEMENTS_LIMIT = 5;
public static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher( UriMatcher.NO_MATCH );
........
uriMatcher.addURI(AUTHORITY, "elements/limit/#", ELEMENTS_LIMIT);
}
Then in your query method
String limit = null; //default....
switch( uriMatcher.match(uri)){
.......
case ELEMENTS_LIMIT:
limit = uri.getPathSegments().get(2);
break;
......
}
return mySQLBuilder.query( db, projection, selection, selectionArgs, null, null, sortOrder, limit );
Querying ContentProvider from Activity.
uri = Uri.parse("content://" + ContentProvider.AUTHORITY + "/elements/limit/" + 1 );
//In My case I want to sort and get the greatest value in an X column. So having the column sorted and limiting to 1 works.
Cursor query = resolver.query(uri,
new String[]{YOUR_COLUMNS},
null,
null,
(X_COLUMN + " desc") );
A content provider should on general principle pay attention to a limit parameter.
Unfortunately, it is not universally implemented.
For instance, when writing a content provider to handle SearchManager queries:
String limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT);
Where it isn't implemented you can only fall back on the ugly option of gluing a limit on the sort clause.

Android custom method in content provider to get number of records in table?

I have a content provider that accesses my database which is fine if you need to deal with record sets but I need a method to return an integer denoting the number of records in a table
The method looks like this
public long getRecordCount(String TableName) {
SQLiteDatabase mDatabase = mOpenHelper.getReadableDatabase();
String sql = "SELECT COUNT(*) FROM " + TableName;
SQLiteStatement statement = mDatabase.compileStatement(sql);
long count = statement.simpleQueryForLong();
return count;
}
But I am unable to find any way of using this (Or any other method that does not return a cursor for that matter) in a content provider so where is the best place to put this method and how to call it?
Obviously I could do the really bad option of selecting all the records with a managed query and using the cursor.count result but that is one hugely inefficient way of dealing with this specific requirement
Thanks
You can also simply use "count(*)" as a projection in a call to your content providers URIs, as in the following helper method
public static int count(Uri uri,String selection,String[] selectionArgs) {
Cursor cursor = getContentResolver().query(uri,new String[] {"count(*)"},
selection, selectionArgs, null);
if (cursor.getCount() == 0) {
cursor.close();
return 0;
} else {
cursor.moveToFirst();
int result = cursor.getInt(0);
cursor.close();
return result;
}
}
One way you can access it is by using the call() method in the ContentResolver class. I can't seem to find much about how to actually use this on google, but my guess is that you should just have your getRecordCount() return a bundle with your result in it. Of course the easier thing to do would be something like what's described in this SO Post.

Calling delete method in custom content provider

I am learning Android and I am stuck on an issue involving calling a custom content provider. I have been using an example in an instructional book and although it describes how to create the custom provider there is no clear example how to call the specific methods in it. I am specifically looking into how to delete a single record from the custom content provider.
Here is the code for the custom content provider (EarthquakeProvider.java):
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case QUAKES:
count = earthquakeDB.delete(EARTHQUAKE_TABLE, where, whereArgs);
break;
case QUAKE_ID:
String segment = uri.getPathSegments().get(1);
count = earthquakeDB.delete(EARTHQUAKE_TABLE, KEY_ID + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
I am trying to call the delete method from the main activity to delete a single entry, not the entire database. I want to use about an OnLongClickListener for the selected record that is displayed in a array list view in the main activity.
This is what I have come up with I have so far in my main activity for this method:
earthquakeListView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView _av, View _v, int _index,
long arg3) {
ContentResolver cr = getContentResolver();
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
return false;
}
I know the above code doesn't work, but this is as close as I could get with my current understanding.
Any help on this would be very much appreciated.
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
This is your problem. First, some context:
Content URIs: (source)
content://authority/path/##
The number at the end is optional. If present, the URI references a specific row in the database where row._id=(the number). If absent, it references the table as a whole.
the delete() call accepts a URI, a where clause, and a set of strings which get substituted in. Example: Say you have a database of people.
cr.delete(
Person.CONTENT_URI,
"sex=? AND eyecolor=?",
new String[]{"male", "blue"});
Will search the entire person table, and delete anyone whose sex is male and whose eye color is blue.
If the where clause and where values are null, then the delete() call will match every row in the table. This causes the behavior you see.
There are two methods to specify the row you want:
First option, you could append the number to the URI:
cr.delete(
EarthquakeProvider.CONTENT_URI.buildUpon().appendPath(String.valueOf(_id)).build(),
null, null);
This restricts the URI to a specific row, and the path will be through your case QUAKE_ID: statement and so will only delete one row no matter what.
Second option, you could use a where clause:
cr.delete(EarthquakeProvider.CONTENT_URI, "_id=?", String.valueOf(_id)));
Either way, you will restrict the delete to a single row, as you need it to. The latter makes for prettier code, but the former is more efficient, due to the way the ContentProvider and ContentObservers work.
As a last note: In your ContentProvider you need to add a call to
ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork). This helps notify cursors to re-fetch the database query and helps out a lot with automation.

Categories

Resources