if (clipData != null && clipData.getItemCount() > 0) { for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item clipItem = clipData.getItemAt(i); if (clipItem != null) {
Uri uri = clipItem.getUri(); if (uri != null) { if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) { try {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (Exception e) {
logger.error("Exception", e);
}
}
uriList.add(uri);
}
}
}
} else { if (returnData != null) { if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(returnData.getScheme())) { try {
contentResolver.takePersistableUriPermission(returnData, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (Exception e) {
logger.error("Exception", e);
}
}
uriList.add(returnData);
}
} return validateUriList(uriList);
}
/** * Check if selected files are located within the app's private directory * * @param uris Uris to check * @return List of Uris not located in the private directory
*/ privatestatic @NonNull ArrayList<Uri> validateUriList(ArrayList<Uri> uris) {
String dataDir = Environment.getDataDirectory().toString();
ArrayList<Uri> validatedUris = new ArrayList<>();
if (uris != null && !uris.isEmpty()) { for (Uri uri : uris) { try { if (uri != null) { if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { // Files from /data may not be sent if coming from a Picker final File f = new File(uri.getPath()); final String filePath = f.getCanonicalPath(); if (filePath.startsWith(dataDir)) { continue;
}
}
validatedUris.add(uri);
}
} catch (Exception e) { //
}
}
/** * Get the mime type by looking at the filename's extension * * @param path filename or complete path of the file * @return Mime Type or application/octet-stream if a mime type could not be determined from the extension
*/
@NonNull publicstatic String getMimeTypeFromPath(@Nullable String path) {
String mimeType = null;
if (path != null) {
String extension = MimeTypeMap.getFileExtensionFromUrl(path); if (TextUtils.isEmpty(extension)) { // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle // urlEncoded strings. Let's try one last time at finding the extension. int dotPos = path.lastIndexOf('.'); if (0 <= dotPos) {
extension = path.substring(dotPos + 1);
}
} if (!TextUtils.isEmpty(extension)) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
} if (extension.equalsIgnoreCase("opus")) { // whatsapp ogg files
mimeType = MimeUtil.MIME_TYPE_AUDIO_OGG;
} elseif (extension.equalsIgnoreCase("gpx")) { // https://issuetracker.google.com/issues/37120151
mimeType = MimeUtil.MIME_TYPE_GPX;
} elseif (extension.equalsIgnoreCase("pkpass")) {
mimeType = MimeUtil.MIME_TYPE_APPLE_PKPASS;
}
} if (TestUtil.isEmptyOrNull(mimeType)) { return MimeUtil.MIME_TYPE_DEFAULT;
} return mimeType;
}
@Nullable publicstatic String getMimeTypeFromUri(@NonNull Context context, @Nullable Uri uri) { if (uri != null) {
ContentResolver contentResolver = context.getContentResolver();
String type = contentResolver.getType(uri);
if (TestUtil.isEmptyOrNull(type) || MimeUtil.MIME_TYPE_DEFAULT.equals(type)) {
String filename = FileUtil.getFilenameFromUri(contentResolver, uri);
/** * Check if Storage Access Framework is really available
*/ privatestaticboolean isMediaProviderSupported(Context context) { final PackageManager pm = context.getPackageManager(); // Pick up provider with action string final Intent i = new Intent(DocumentsContract.PROVIDER_INTERFACE); final List<ResolveInfo> providers = pm.queryIntentContentProviders(i, 0); for (ResolveInfo info : providers) { if (info != null && info.providerInfo != null) { final String authority = info.providerInfo.authority; if (isMediaDocument(Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + authority))) returntrue;
}
} returnfalse;
}
@Nullable publicstatic String getRealPathFromURI(@NonNull final Context context, @NonNull final Uri uri) { // DocumentProvider if (DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0];
if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1];
}
} // DownloadsProvider elseif (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri);
if (id != null) { if (id.startsWith("raw:/")) { return id.substring(4);
} else { try { final Uri contentUri = ContentUris.withAppendedId(
Uri.parse(ContentResolver.SCHEME_CONTENT + "://downloads/public_downloads"), Long.parseLong(id)); return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
logger.info("Unable to extract document ID. Giving up.");
}
}
} else {
logger.info("No document ID. Giving up.");
}
} // MediaProvider elseif (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0];
publicstatic @NonNull String getMediaFilenamePrefix() {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.getDefault()); return"threema-" + format.format(System.currentTimeMillis());
}
/** * Return a default filename keeping in account specified mime type * * @param mimeType the mime type to generate a filename for * @return a filename with an extension
*/ publicstatic @NonNull String getDefaultFilename(@Nullable String mimeType) { if (TestUtil.isEmptyOrNull(mimeType)) {
mimeType = MimeUtil.MIME_TYPE_DEFAULT;
}
/** * Attempt to delete a file. If deleting fails, log a warning using the specified logger. * <p> * Note: Do not use this if error recovery is important! * * @param file The file that should be deleted * @param description The description of the file (e.g. "message queue database") * @param logger The logger to use
*/ publicstaticvoid deleteFileOrWarn(
@NonNull File file,
@Nullable String description,
@NonNull Logger logger
) { if (!file.delete()) {
logger.warn("Could not delete {}", description);
}
}
/** * Create a new file or re-use existing file. Log if file already exists. * * @param file The file that should be created or re-used * @param logger The logger facility to use
*/ publicstaticvoid createNewFileOrLog(
@NonNull File file,
@NonNull Logger logger
) throws IOException { if (!file.createNewFile()) {
logger.debug("File {} already exists", file.getAbsolutePath());
}
}
/** * Try to generated a File with the given filename in the given path * If a file of the same name exists, add a number to the filename (possibly between name and extension) * * @param destPath Destination path * @param destFilename Desired filename * @return File object
*/ publicstatic File getUniqueFile(String destPath, String destFilename) {
File destFile = new File(destPath, destFilename);
int i = 0; while (destFile.exists()) {
i++;
destFile = new File(destPath, filePart + " (" + i + ")" + extension); if (!destFile.exists()) { break;
}
}
return destFile;
}
/** * Returns the filename of the object referred to by mediaItem. If no filename can be found, generate one * * @param contentResolver ContentResolver * @param mediaItem MediaItem representing the source file * @return A filename
*/ publicstatic @NonNull String getFilenameFromUri(@NonNull ContentResolver contentResolver, @NonNull MediaItem mediaItem) {
String filename = getFilenameFromUri(contentResolver, mediaItem.getUri());
if (TextUtils.isEmpty(filename)) {
filename = getDefaultFilename(mediaItem.getMimeType());
} return filename;
}
/** * Returns the filename of the object referred to by uri by querying the content resolver * * @param contentResolver ContentResolver * @param uri Uri pointing at the object * @return A filename or null if none is found
*/
@Nullable publicstatic String getFilenameFromUri(ContentResolver contentResolver, Uri uri) {
String filename = null;
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
filename = uri.getLastPathSegment();
} else { try (final Cursor cursor = contentResolver.query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToNext()) {
filename = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME));
}
} catch (Exception e) {
logger.error("Unable to query Content Resolver", e); // guess filename from last path segment of content URI. Accept only filenames that contain one "." character
String lastPathSegment = uri.getLastPathSegment(); if (lastPathSegment != null) { if (lastPathSegment.indexOf(".") == lastPathSegment.lastIndexOf(".")) {
filename = lastPathSegment;
}
}
}
} return filename;
}
/** * Try to get a file uri from a content uri to maintain access to a file across two activities. * This method does not sanitize the uri and allows converting the uri to a file uri that can * refer internal files. * NOTE: This hack will probably stop working in API 30 * * @param uri content uri to resolve * @return file uri, if a file path could be resolved
*/
@NonNull publicstatic Uri getFileUri(@NonNull Uri uri) {
String path = FileUtil.getRealPathFromURI(ThreemaApplication.getAppContext(), uri);
if (path != null) {
File file = new File(path); if (file.canRead()) { return Uri.fromFile(file);
}
} return uri;
}
/** * Select a file from a gallery app. Shows a selector first to allow for choosing the desired gallery app or SystemUIs file picker. * Does not necessarily need file permissions as a modern gallery app will return a content Uri with a temporary permission to access the file * * @param activity Activity where the result of the selection should end up * @param fragment Fragment where the result of the selection should end up * @param requestCode Request code to use for result * @param includeVideo Whether to include the possibility to select video files (if supported by app)
*/ publicstaticvoid selectFromGallery(@Nullable Activity activity, @Nullable Fragment fragment, int requestCode, boolean includeVideo) { if (activity == null) {
activity = fragment.getActivity();
}
final String imageMimeTypes = String.join(",", MimeUtil.getSupportedImageMimeTypes());
try { final Intent startIntent; final Intent getContentIntent = new Intent();
/** * Check if the file at the provided Uri is an animated WebP file by looking at the file header * * @param uri A File Uri pointing to an image file * @return true if the file is an animated WebP file, false if it is not animated, in another format, corrupt or not readable
*/ privatestaticboolean isAnimatedWebPFile(@NonNull Uri uri) { try (InputStream inputStream = getFromUri(ThreemaApplication.getAppContext(), uri)) { byte[] buffer = newbyte[34]; return inputStream != null
&& inputStream.read(buffer) == 34
&& Arrays.equals(Arrays.copyOfRange(buffer, 0, 4), newbyte[]{'R', 'I', 'F', 'F'})
&& Arrays.equals(Arrays.copyOfRange(buffer, 8, 12), newbyte[]{'W', 'E', 'B', 'P'})
&& Arrays.equals(Arrays.copyOfRange(buffer, 12, 15), newbyte[]{'V', 'P', '8'})
&& Arrays.equals(Arrays.copyOfRange(buffer, 30, 34), newbyte[]{'A', 'N', 'I', 'M'});
} catch (IOException ignore) { returnfalse;
}
}
/** * Check if the file at the provided Uri is an animation. Currently, only animated WebP and (possibly static) GIFs are supported * * @param uri A File Uri pointing to an image file * @return true if the file an animated image
*/ publicstaticboolean isAnimatedImageFile(@NonNull Uri uri, String mimeType) { return isAnimatedWebPFile(uri) || MimeUtil.isGifFile(mimeType);
}
/** * Checks that the file path is not inside the app's internal data directory after resolving * path traversals. * * @param context the context * @param path the path that will be checked * @return {@code false} if the provided path is null, contains unresolvable path traversals, or * resides inside the internal data directory
*/ publicstaticboolean isSanePath(@NonNull Context context, @Nullable String path) { if (path == null) { // We are rather restrictive here. In general it is safer to assume that null paths are // not safe. returnfalse;
}
// Check for path traversals if (canonicalPath.contains("..")) {
logger.warn("Path traversal attempted"); returnfalse;
}
File dataDir = ContextCompat.getDataDir(context); if (dataDir == null) {
logger.error("Could not determine data directory"); returnfalse;
}
String canonicalDataDir; try {
canonicalDataDir = dataDir.getCanonicalPath();
} catch (IOException exception) {
logger.warn("Cannot get canonical path of data dir"); returnfalse;
}
// Check that the path lies not inside the private files directory if (canonicalPath.startsWith(canonicalDataDir)) {
logger.warn("Access denied to data dir"); returnfalse;
}
returntrue;
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet am 2026-04-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.