Prevent part files from being deleted prematurely.

This commit is contained in:
Cody Henthorne 2021-09-14 10:49:21 -04:00 committed by Alex Hart
parent 662ba85c5a
commit e2cb522e87
4 changed files with 75 additions and 18 deletions

View file

@ -162,9 +162,8 @@ public class FullBackupImporter extends FullBackupBase {
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
throws IOException
{
File partsDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE);
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
File dataFile = AttachmentDatabase.newFile(context);
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
ContentValues contentValues = new ContentValues();

View file

@ -4,11 +4,8 @@ import android.view.View
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingViewHolder
import java.util.UUID
object InternalPreference {

View file

@ -73,6 +73,7 @@ import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -82,9 +83,10 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class AttachmentDatabase extends Database {
private static final String TAG = Log.tag(AttachmentDatabase.class);
public static final String TABLE_NAME = "part";
@ -122,7 +124,7 @@ public class AttachmentDatabase extends Database {
static final String UPLOAD_TIMESTAMP = "upload_timestamp";
static final String CDN_NUMBER = "cdn_number";
public static final String DIRECTORY = "parts";
private static final String DIRECTORY = "parts";
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
@ -475,14 +477,18 @@ public class AttachmentDatabase extends Database {
}
public int deleteAbandonedAttachmentFiles() {
Set<String> filesOnDisk = new HashSet<>();
Set<String> filesInDb = new HashSet<>();
File[] diskFiles = context.getDir(DIRECTORY, Context.MODE_PRIVATE).listFiles();
File attachmentDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
for (File file : attachmentDirectory.listFiles()) {
filesOnDisk.add(file.getAbsolutePath());
if (diskFiles == null) {
return 0;
}
Set<String> filesOnDisk = Arrays.stream(diskFiles)
.filter(f -> !PartFileProtector.isProtected(f))
.map(File::getAbsolutePath)
.collect(Collectors.toSet());
Set<String> filesInDb = new HashSet<>();
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(true, TABLE_NAME, new String[] { DATA }, null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
filesInDb.add(CursorUtil.requireString(cursor, DATA));
@ -866,10 +872,8 @@ public class AttachmentDatabase extends Database {
return existing;
}
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File transferFile = File.createTempFile("transfer", ".mms", partsDirectory);
ContentValues values = new ContentValues();
File transferFile = newTransferFile();
ContentValues values = new ContentValues();
values.put(TRANSFER_FILE, transferFile.getAbsolutePath());
db.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
@ -1066,8 +1070,17 @@ public class AttachmentDatabase extends Database {
}
public File newFile() throws IOException {
return newFile(context);
}
private File newTransferFile() throws IOException {
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
return File.createTempFile("part", ".mms", partsDirectory);
return PartFileProtector.protect(() -> File.createTempFile("transfer", ".mms", partsDirectory));
}
public static File newFile(Context context) throws IOException {
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
return PartFileProtector.protect(() -> File.createTempFile("part", ".mms", partsDirectory));
}
private @NonNull DataInfo setAttachmentData(@NonNull File destination,
@ -1085,6 +1098,7 @@ public class AttachmentDatabase extends Database {
if (!tempFile.renameTo(destination)) {
Log.w(TAG, "Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
tempFile.delete();
throw new IllegalStateException("Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
}

View file

@ -0,0 +1,47 @@
package org.thoughtcrime.securesms.database;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* In-memory book keeping of newly created part files to prevent them from being
* deleted prematurely.
*/
public final class PartFileProtector {
private static final long PROTECTED_DURATION = TimeUnit.MINUTES.toMillis(10);
private static final Map<String, Long> protectedFiles = new HashMap<>();
public static synchronized File protect(@NonNull CreateFile createFile) throws IOException {
File file = createFile.create();
protectedFiles.put(file.getAbsolutePath(), System.currentTimeMillis());
return file;
}
public static synchronized boolean isProtected(File file) {
long timestamp = 0;
Long protectedTimestamp = protectedFiles.get(file.getAbsolutePath());
if (protectedTimestamp != null) {
timestamp = Math.max(protectedTimestamp, file.lastModified());
}
boolean isProtected = timestamp > System.currentTimeMillis() - PROTECTED_DURATION;
if (!isProtected) {
protectedFiles.remove(file.getAbsolutePath());
}
return isProtected;
}
interface CreateFile {
@NonNull File create() throws IOException;
}
}