|
|
@ -12,8 +12,10 @@ import android.webkit.MimeTypeMap;
|
|
|
|
|
|
|
|
|
|
|
|
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
|
|
|
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
|
|
|
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
|
|
|
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
|
|
|
|
|
|
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
|
|
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
|
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
|
|
|
|
|
|
import org.whispersystems.libsignal.InvalidMessageException;
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.File;
|
|
|
@ -30,15 +32,23 @@ public class PersistentBlobProvider {
|
|
|
|
|
|
|
|
|
|
|
|
private static final String TAG = PersistentBlobProvider.class.getSimpleName();
|
|
|
|
private static final String TAG = PersistentBlobProvider.class.getSimpleName();
|
|
|
|
|
|
|
|
|
|
|
|
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture";
|
|
|
|
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture-new";
|
|
|
|
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
|
|
|
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
|
|
|
public static final String AUTHORITY = "org.thoughtcrime.securesms";
|
|
|
|
public static final String AUTHORITY = "org.thoughtcrime.securesms";
|
|
|
|
public static final String EXPECTED_PATH = "capture/*/*/#";
|
|
|
|
public static final String EXPECTED_PATH_OLD = "capture/*/*/#";
|
|
|
|
|
|
|
|
public static final String EXPECTED_PATH_NEW = "capture-new/*/*/*/*/#";
|
|
|
|
|
|
|
|
|
|
|
|
private static final int MIMETYPE_PATH_SEGMENT = 1;
|
|
|
|
private static final int MIMETYPE_PATH_SEGMENT = 1;
|
|
|
|
|
|
|
|
private static final int FILENAME_PATH_SEGMENT = 2;
|
|
|
|
|
|
|
|
private static final int FILESIZE_PATH_SEGMENT = 3;
|
|
|
|
|
|
|
|
|
|
|
|
private static final String BLOB_EXTENSION = "blob";
|
|
|
|
private static final String BLOB_EXTENSION = "blob";
|
|
|
|
private static final int MATCH = 1;
|
|
|
|
private static final int MATCH_OLD = 1;
|
|
|
|
|
|
|
|
private static final int MATCH_NEW = 2;
|
|
|
|
|
|
|
|
|
|
|
|
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
|
|
|
|
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
|
|
|
|
addURI(AUTHORITY, EXPECTED_PATH, MATCH);
|
|
|
|
addURI(AUTHORITY, EXPECTED_PATH_OLD, MATCH_OLD);
|
|
|
|
|
|
|
|
addURI(AUTHORITY, EXPECTED_PATH_NEW, MATCH_NEW);
|
|
|
|
}};
|
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
|
|
private static volatile PersistentBlobProvider instance;
|
|
|
|
private static volatile PersistentBlobProvider instance;
|
|
|
@ -65,24 +75,35 @@ public class PersistentBlobProvider {
|
|
|
|
|
|
|
|
|
|
|
|
public Uri create(@NonNull MasterSecret masterSecret,
|
|
|
|
public Uri create(@NonNull MasterSecret masterSecret,
|
|
|
|
@NonNull byte[] blobBytes,
|
|
|
|
@NonNull byte[] blobBytes,
|
|
|
|
@NonNull String mimeType)
|
|
|
|
@NonNull String mimeType,
|
|
|
|
|
|
|
|
@Nullable String fileName)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
final long id = System.currentTimeMillis();
|
|
|
|
final long id = System.currentTimeMillis();
|
|
|
|
cache.put(id, blobBytes);
|
|
|
|
cache.put(id, blobBytes);
|
|
|
|
return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType);
|
|
|
|
return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType, fileName, (long) blobBytes.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Uri create(@NonNull MasterSecret masterSecret,
|
|
|
|
public Uri create(@NonNull MasterSecret masterSecret,
|
|
|
|
@NonNull InputStream input,
|
|
|
|
@NonNull InputStream input,
|
|
|
|
@NonNull String mimeType)
|
|
|
|
@NonNull String mimeType,
|
|
|
|
|
|
|
|
@Nullable String fileName,
|
|
|
|
|
|
|
|
@Nullable Long fileSize)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return create(masterSecret, input, System.currentTimeMillis(), mimeType);
|
|
|
|
return create(masterSecret, input, System.currentTimeMillis(), mimeType, fileName, fileSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Uri create(MasterSecret masterSecret, InputStream input, long id, String mimeType) {
|
|
|
|
private Uri create(@NonNull MasterSecret masterSecret,
|
|
|
|
|
|
|
|
@NonNull InputStream input,
|
|
|
|
|
|
|
|
long id,
|
|
|
|
|
|
|
|
@NonNull String mimeType,
|
|
|
|
|
|
|
|
@Nullable String fileName,
|
|
|
|
|
|
|
|
@Nullable Long fileSize)
|
|
|
|
|
|
|
|
{
|
|
|
|
persistToDisk(masterSecret, id, input);
|
|
|
|
persistToDisk(masterSecret, id, input);
|
|
|
|
final Uri uniqueUri = CONTENT_URI.buildUpon()
|
|
|
|
final Uri uniqueUri = CONTENT_URI.buildUpon()
|
|
|
|
.appendPath(mimeType)
|
|
|
|
.appendPath(mimeType)
|
|
|
|
|
|
|
|
.appendPath(getEncryptedFileName(masterSecret, fileName))
|
|
|
|
|
|
|
|
.appendEncodedPath(String.valueOf(fileSize))
|
|
|
|
.appendEncodedPath(String.valueOf(System.currentTimeMillis()))
|
|
|
|
.appendEncodedPath(String.valueOf(System.currentTimeMillis()))
|
|
|
|
.build();
|
|
|
|
.build();
|
|
|
|
return ContentUris.withAppendedId(uniqueUri, id);
|
|
|
|
return ContentUris.withAppendedId(uniqueUri, id);
|
|
|
@ -113,13 +134,14 @@ public class PersistentBlobProvider {
|
|
|
|
|
|
|
|
|
|
|
|
public boolean delete(@NonNull Uri uri) {
|
|
|
|
public boolean delete(@NonNull Uri uri) {
|
|
|
|
switch (MATCHER.match(uri)) {
|
|
|
|
switch (MATCHER.match(uri)) {
|
|
|
|
case MATCH:
|
|
|
|
case MATCH_OLD:
|
|
|
|
|
|
|
|
case MATCH_NEW:
|
|
|
|
long id = ContentUris.parseId(uri);
|
|
|
|
long id = ContentUris.parseId(uri);
|
|
|
|
cache.remove(id);
|
|
|
|
cache.remove(id);
|
|
|
|
return getFile(ContentUris.parseId(uri)).delete();
|
|
|
|
return getFile(ContentUris.parseId(uri)).delete();
|
|
|
|
default:
|
|
|
|
|
|
|
|
return new File(uri.getPath()).delete();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
|
|
|
|
public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
|
|
|
@ -132,6 +154,11 @@ public class PersistentBlobProvider {
|
|
|
|
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION);
|
|
|
|
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private @Nullable String getEncryptedFileName(@NonNull MasterSecret masterSecret, @Nullable String fileName) {
|
|
|
|
|
|
|
|
if (fileName == null) return null;
|
|
|
|
|
|
|
|
return new MasterCipher(masterSecret).encryptBody(fileName);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) {
|
|
|
|
public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) {
|
|
|
|
if (!isAuthority(context, persistentBlobUri)) return null;
|
|
|
|
if (!isAuthority(context, persistentBlobUri)) return null;
|
|
|
|
return isExternalBlobUri(context, persistentBlobUri)
|
|
|
|
return isExternalBlobUri(context, persistentBlobUri)
|
|
|
@ -139,6 +166,35 @@ public class PersistentBlobProvider {
|
|
|
|
: persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
|
|
|
|
: persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static @Nullable String getFileName(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri persistentBlobUri) {
|
|
|
|
|
|
|
|
if (!isAuthority(context, persistentBlobUri)) return null;
|
|
|
|
|
|
|
|
if (isExternalBlobUri(context, persistentBlobUri)) return null;
|
|
|
|
|
|
|
|
if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String fileName = persistentBlobUri.getPathSegments().get(FILENAME_PATH_SEGMENT);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return new MasterCipher(masterSecret).decryptBody(fileName);
|
|
|
|
|
|
|
|
} catch (InvalidMessageException e) {
|
|
|
|
|
|
|
|
Log.w(TAG, "No valid filename for URI");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static @Nullable Long getFileSize(@NonNull Context context, Uri persistentBlobUri) {
|
|
|
|
|
|
|
|
if (!isAuthority(context, persistentBlobUri)) return null;
|
|
|
|
|
|
|
|
if (isExternalBlobUri(context, persistentBlobUri)) return null;
|
|
|
|
|
|
|
|
if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return Long.valueOf(persistentBlobUri.getPathSegments().get(FILESIZE_PATH_SEGMENT));
|
|
|
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
|
|
Log.w(TAG, e);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static @NonNull String getExtensionFromMimeType(String mimeType) {
|
|
|
|
private static @NonNull String getExtensionFromMimeType(String mimeType) {
|
|
|
|
final String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
|
|
|
|
final String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
|
|
|
|
return extension != null ? extension : BLOB_EXTENSION;
|
|
|
|
return extension != null ? extension : BLOB_EXTENSION;
|
|
|
@ -157,7 +213,8 @@ public class PersistentBlobProvider {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static boolean isAuthority(@NonNull Context context, @NonNull Uri uri) {
|
|
|
|
public static boolean isAuthority(@NonNull Context context, @NonNull Uri uri) {
|
|
|
|
return MATCHER.match(uri) == MATCH || isExternalBlobUri(context, uri);
|
|
|
|
int matchResult = MATCHER.match(uri);
|
|
|
|
|
|
|
|
return matchResult == MATCH_NEW || matchResult == MATCH_OLD || isExternalBlobUri(context, uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static boolean isExternalBlobUri(@NonNull Context context, @NonNull Uri uri) {
|
|
|
|
private static boolean isExternalBlobUri(@NonNull Context context, @NonNull Uri uri) {
|
|
|
|