Add temporary file storage as discussed in #665

Writable from OpenKeychain, readable worldwide. Should be used to write shared files to it by first creating the file using TemporaryStorageProvider.createFile and then write to the Uri returned.
This commit is contained in:
mar-v-in 2014-07-01 14:50:15 +02:00
parent 50e72b196f
commit 3564773410
5 changed files with 201 additions and 0 deletions

View File

@ -49,6 +49,9 @@
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"/>
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
@ -484,6 +487,12 @@
android:resource="@xml/custom_pgp_contacts_structure"/> android:resource="@xml/custom_pgp_contacts_structure"/>
</service> </service>
<provider
android:name=".provider.TemporaryStorageProvider"
android:authorities="org.sufficientlysecure.keychain.tempstorage"
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"
android:exported="true"/>
</application> </application>
</manifest> </manifest>

View File

@ -53,6 +53,8 @@ public final class Constants {
public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
public static int TEMPFILE_TTL = 24*60*60*1000; // 1 day
public static final class Path { public static final class Path {
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain");
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc"); public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");

View File

@ -28,6 +28,7 @@ import android.os.Environment;
import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.helper.TlsHelper; import org.sufficientlysecure.keychain.helper.TlsHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PRNGFixes; import org.sufficientlysecure.keychain.util.PRNGFixes;
@ -85,6 +86,8 @@ public class KeychainApplication extends Application {
Preferences.getPreferences(this).updateKeyServers(); Preferences.getPreferences(this).updateKeyServers();
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer"); TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
TemporaryStorageProvider.cleanUp(this);
} }
public static void setupAccountAsNeeded(Context context) { public static void setupAccountAsNeeded(Context context) {

View File

@ -0,0 +1,151 @@
package org.sufficientlysecure.keychain.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TemporaryStorageProvider extends ContentProvider {
private static final String DB_NAME = "tempstorage.db";
private static final String TABLE_FILES = "files";
private static final String COLUMN_ID = "id";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TIME = "time";
private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
private static final int DB_VERSION = 1;
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
return context.getContentResolver().insert(BASE_URI, contentValues);
}
public static int cleanUp(Context context) {
return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
}
private class TemporaryStorageDatabase extends SQLiteOpenHelper {
public TemporaryStorageDatabase(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_NAME + " TEXT, " +
COLUMN_TIME + " INTEGER" +
");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
private TemporaryStorageDatabase db;
private File getFile(Uri uri) throws FileNotFoundException {
try {
return getFile(Integer.parseInt(uri.getLastPathSegment()));
} catch (NumberFormatException e) {
throw new FileNotFoundException();
}
}
private File getFile(int id) {
return new File(getContext().getCacheDir(), "temp/" + id);
}
@Override
public boolean onCreate() {
db = new TemporaryStorageDatabase(getContext());
return new File(getContext().getCacheDir(), "temp").mkdirs();
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
File file;
try {
file = getFile(uri);
} catch (FileNotFoundException e) {
return null;
}
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
if (fileName.moveToNext()) {
MatrixCursor cursor =
new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"});
cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath());
fileName.close();
return cursor;
}
fileName.close();
}
return null;
}
@Override
public String getType(Uri uri) {
return "*/*";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if (!values.containsKey(COLUMN_TIME)) {
values.put(COLUMN_TIME, System.currentTimeMillis());
}
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
try {
getFile(insert).createNewFile();
} catch (IOException e) {
return null;
}
return Uri.withAppendedPath(BASE_URI, Long.toString(insert));
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri.getLastPathSegment() != null) {
selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
}
Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection,
selectionArgs, null, null, null);
if (files != null) {
while (files.moveToNext()) {
getFile(files.getInt(0)).delete();
}
files.close();
return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs);
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Update not supported");
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return openFileHelper(uri, mode);
}
}

View File

@ -0,0 +1,36 @@
package org.sufficientlysecure.keychain.util;
import android.text.TextUtils;
/**
* Shamelessly copied from android.database.DatabaseUtils
*/
public class DatabaseUtil {
/**
* Concatenates two SQL WHERE clauses, handling empty or null values.
*/
public static String concatenateWhere(String a, String b) {
if (TextUtils.isEmpty(a)) {
return b;
}
if (TextUtils.isEmpty(b)) {
return a;
}
return "(" + a + ") AND (" + b + ")";
}
/**
* Appends one set of selection args to another. This is useful when adding a selection
* argument to a user provided set.
*/
public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
if (originalValues == null || originalValues.length == 0) {
return newValues;
}
String[] result = new String[originalValues.length + newValues.length ];
System.arraycopy(originalValues, 0, result, 0, originalValues.length);
System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
return result;
}
}