4.3.1.2 创建/使用公共内容供应器
公共内容供应器是应该由未指定的大量应用使用的内容供应器。 需要注意的是,由于它不指定客户端,它可能会受到恶意软件的攻击和篡改。 例如,可以通过select()获取保存的数据,可以通过update()更改数据,或者可以通过insert()/ delete()插入/删除假数据。
另外,在使用 Android OS 未提供的自定义公共内容供应器时,需要注意的是,恶意软件可能会接收到请求参数,伪装成自定义公共内容供应器,并且也可能发送攻击数据。 Android OS 提供的联系人和 MediaStore 也是公共内容提供商,但恶意软件不能伪装成它们。
实现公共内容供应器的样例代码展示在下面:
要点(创建内容供应器):
将导出的属性显式设置为
true。仔细安全地处理收到的请求数据。
返回结果时,请勿包含敏感信息。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.provider.publicprovider"><applicationandroid:icon="@drawable/ic_launcher"android:label="@string/app_name" ><!-- *** POINT 1 *** Explicitly set the exported attribute to true. --><providerandroid:name=".PublicProvider"android:authorities="org.jssec.android.provider.publicprovider"android:exported="true" /></application></manifest>
PublicProvider.java
package org.jssec.android.provider.publicprovider;import android.content.ContentProvider;import android.content.ContentUris;import android.content.ContentValues;import android.content.UriMatcher;import android.database.Cursor;import android.database.MatrixCursor;import android.net.Uri;public class PublicProvider extends ContentProvider {public static final String AUTHORITY = "org.jssec.android.provider.publicprovider";public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";// Expose the interface that the Content Provider provides.public interface Download {public static final String PATH = "downloads";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);}public interface Address {public static final String PATH = "addresses";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);}// UriMatcherprivate static final int DOWNLOADS_CODE = 1;private static final int DOWNLOADS_ID_CODE = 2;private static final int ADDRESSES_CODE = 3;private static final int ADDRESSES_ID_CODE = 4;private static UriMatcher sUriMatcher;static {sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);}// Since this is a sample program,// query method returns the following fixed result always without using database.private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });static {sAddressCursor.addRow(new String[] { "1", "New York" });sAddressCursor.addRow(new String[] { "2", "London" });sAddressCursor.addRow(new String[] { "3", "Paris" });}private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });static {sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });}@Overridepublic boolean onCreate() {return true;}@Overridepublic String getType(Uri uri) {switch (sUriMatcher.match(uri)) {case DOWNLOADS_CODE:case ADDRESSES_CODE:return CONTENT_TYPE;case DOWNLOADS_ID_CODE:case ADDRESSES_ID_CODE:return CONTENT_ITEM_TYPE;default:throw new IllegalArgumentException("Invalid URI:" + uri);}}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// *** POINT 2 *** Handle the received request data carefully and securely.// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.// Checking for other parameters are omitted here, due to sample.// Refer to "3.2 Handle Input Data Carefully and Securely."// *** POINT 3 *** When returning a result, do not include sensitive information.// It depends on application whether the query result has sensitive meaning or not.// If no problem when the information is taken by malware, it can be returned as result.switch (sUriMatcher.match(uri)) {case DOWNLOADS_CODE:case DOWNLOADS_ID_CODE:return sDownloadCursor;case ADDRESSES_CODE:case ADDRESSES_ID_CODE:return sAddressCursor;default:throw new IllegalArgumentException("Invalid URI:" + uri);}}@Overridepublic Uri insert(Uri uri, ContentValues values) {// *** POINT 2 *** Handle the received request data carefully and securely.// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.// Checking for other parameters are omitted here, due to sample.// Refer to "3.2 Handle Input Data Carefully and Securely."// *** POINT 3 *** When returning a result, do not include sensitive information.// It depends on application whether the issued ID has sensitive meaning or not.// If no problem when the information is taken by malware, it can be returned as result.switch (sUriMatcher.match(uri)) {case DOWNLOADS_CODE:return ContentUris.withAppendedId(Download.CONTENT_URI, 3);case ADDRESSES_CODE:return ContentUris.withAppendedId(Address.CONTENT_URI, 4);default:throw new IllegalArgumentException("Invalid URI:" + uri);}}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// *** POINT 2 *** Handle the received request data carefully and securely.// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.// Checking for other parameters are omitted here, due to sample.// Refer to "3.2 Handle Input Data Carefully and Securely."// *** POINT 3 *** When returning a result, do not include sensitive information.// It depends on application whether the number of updated records has sensitive meaning or not.// If no problem when the information is taken by malware, it can be returned as result.switch (sUriMatcher.match(uri)) {case DOWNLOADS_CODE:return 5; // Return number of updated recordscase DOWNLOADS_ID_CODE:return 1;case ADDRESSES_CODE:return 15;case ADDRESSES_ID_CODE:return 1;default:throw new IllegalArgumentException("Invalid URI:" + uri);}}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// *** POINT 2 *** Handle the received request data carefully and securely.// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.// Checking for other parameters are omitted here, due to sample.// Refer to "3.2 Handle Input Data Carefully and Securely."// *** POINT 3 *** When returning a result, do not include sensitive information.// It depends on application whether the number of deleted records has sensitive meaning or not.// If no problem when the information is taken by malware, it can be returned as result.switch (sUriMatcher.match(uri)) {case DOWNLOADS_CODE:return 10; // Return number of deleted recordscase DOWNLOADS_ID_CODE:return 1;case ADDRESSES_CODE:return 20;case ADDRESSES_ID_CODE:return 1;default:throw new IllegalArgumentException("Invalid URI:" + uri);}}}
下面是使用公共内容供应器的活动示例。
要点(使用内容供应器):
不要发送敏感信息
收到结果时,小心和安全地处理结果数据
PublicUserActivity.java
package org.jssec.android.provider.publicuser;import android.app.Activity;import android.content.ContentValues;import android.content.pm.ProviderInfo;import android.database.Cursor;import android.net.Uri;import android.os.Bundle;import android.view.View;import android.widget.TextView;public class PublicUserActivity extends Activity {// Target Content Provider Informationprivate static final String AUTHORITY = "org.jssec.android.provider.publicprovider";private interface Address {public static final String PATH = "addresses";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);}public void onQueryClick(View view) {logLine("[Query]");if (!providerExists(Address.CONTENT_URI)) {logLine(" Content Provider doesn't exist.");return;}Cursor cursor = null;try {// *** POINT 4 *** Do not send sensitive information.// since the target Content Provider may be malware.// If no problem when the information is taken by malware, it can be included in the request.cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);// *** POINT 5 *** When receiving a result, handle the result data carefully and securely.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."if (cursor == null) {logLine(" null cursor");} else {boolean moved = cursor.moveToFirst();while (moved) {logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));moved = cursor.moveToNext();}}}finally {if (cursor != null) cursor.close();}}public void onInsertClick(View view) {logLine("[Insert]");if (!providerExists(Address.CONTENT_URI)) {logLine(" Content Provider doesn't exist.");return;}// *** POINT 4 *** Do not send sensitive information.// since the target Content Provider may be malware.// If no problem when the information is taken by malware, it can be included in the request.ContentValues values = new ContentValues();values.put("city", "Tokyo");Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);// *** POINT 5 *** When receiving a result, handle the result data carefully and securely.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."logLine(" uri:" + uri);}public void onUpdateClick(View view) {logLine("[Update]");if (!providerExists(Address.CONTENT_URI)) {logLine(" Content Provider doesn't exist.");return;}// *** POINT 4 *** Do not send sensitive information.// since the target Content Provider may be malware.// If no problem when the information is taken by malware, it can be included in the request.ContentValues values = new ContentValues();values.put("city", "Tokyo");String where = "_id = ?";String[] args = { "4" };int count = getContentResolver().update(Address.CONTENT_URI, values, where, args);// *** POINT 5 *** When receiving a result, handle the result data carefully and securely.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."logLine(String.format(" %s records updated", count));}public void onDeleteClick(View view) {logLine("[Delete]");if (!providerExists(Address.CONTENT_URI)) {logLine(" Content Provider doesn't exist.");return;}// *** POINT 4 *** Do not send sensitive information.// since the target Content Provider may be malware.// If no problem when the information is taken by malware, it can be included in the request.int count = getContentResolver().delete(Address.CONTENT_URI, null, null);// *** POINT 5 *** When receiving a result, handle the result data carefully and securely.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."logLine(String.format(" %s records deleted", count));}private boolean providerExists(Uri uri) {ProviderInfo pi = getPackageManager().resolveContentProvider(uri.getAuthority(), 0);return (pi != null);}private TextView mLogView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mLogView = (TextView)findViewById(R.id.logview);}private void logLine(String line) {mLogView.append(line);mLogView.append("¥n");}}
