From 3e518a6a75a26e64edd6b1f4643640c2459f5407 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Oct 2021 04:16:50 +0500 Subject: [PATCH] Fixed: Fix termux-open failing to open files with "#" and remove hardcoded "content" and "file" strings termux-open "/data/data/com.termux/files/home/te#st.sh" --- .../com/termux/app/TermuxOpenReceiver.java | 18 +++-- .../java/com/termux/shared/data/UriUtils.java | 75 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 termux-shared/src/main/java/com/termux/shared/data/UriUtils.java diff --git a/app/src/main/java/com/termux/app/TermuxOpenReceiver.java b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java index 3a85552b..7042ddd1 100644 --- a/app/src/main/java/com/termux/app/TermuxOpenReceiver.java +++ b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java @@ -2,6 +2,7 @@ package com.termux.app; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -15,6 +16,7 @@ import android.webkit.MimeTypeMap; import com.termux.app.utils.PluginUtils; import com.termux.shared.data.IntentUtils; +import com.termux.shared.data.UriUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; @@ -37,8 +39,8 @@ public class TermuxOpenReceiver extends BroadcastReceiver { } Logger.logVerbose(LOG_TAG, "Intent Received:\n" + IntentUtils.getIntentString(intent)); + Logger.logVerbose(LOG_TAG, "uri: \"" + data + "\", path: \"" + data.getPath() + "\", fragment: \"" + data.getFragment() + "\""); - final String filePath = data.getPath(); final String contentTypeExtra = intent.getStringExtra("content-type"); final boolean useChooser = intent.getBooleanExtra("chooser", false); final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction(); @@ -52,8 +54,8 @@ public class TermuxOpenReceiver extends BroadcastReceiver { break; } - final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file"); - if (isExternalUrl) { + String scheme = data.getScheme(); + if (scheme != null && !ContentResolver.SCHEME_FILE.equals(scheme)) { Intent urlIntent = new Intent(intentAction, data); if (intentAction.equals(Intent.ACTION_SEND)) { urlIntent.putExtra(Intent.EXTRA_TEXT, data.toString()); @@ -70,6 +72,9 @@ public class TermuxOpenReceiver extends BroadcastReceiver { return; } + // Get full path including fragment (anything after last "#") + String filePath = UriUtils.getUriFilePath(data); + final File fileToShare = new File(filePath); if (!(fileToShare.isFile() && fileToShare.canRead())) { Logger.logError(LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'"); @@ -93,7 +98,8 @@ public class TermuxOpenReceiver extends BroadcastReceiver { contentTypeToUse = contentTypeExtra; } - Uri uriToShare = Uri.parse("content://" + TermuxConstants.TERMUX_FILE_SHARE_URI_AUTHORITY + fileToShare.getAbsolutePath()); + // Do not create Uri with Uri.parse() and use Uri.Builder().path(), check UriUtils.getUriFilePath(). + Uri uriToShare = UriUtils.getContentUri(TermuxConstants.TERMUX_FILE_SHARE_URI_AUTHORITY, fileToShare.getAbsolutePath()); if (Intent.ACTION_SEND.equals(intentAction)) { sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare); @@ -184,8 +190,8 @@ public class TermuxOpenReceiver extends BroadcastReceiver { File file = new File(uri.getPath()); try { String path = file.getCanonicalPath(); - String callingPackage = getCallingPackage(); - Logger.logDebug(LOG_TAG, "Open file request received from " + callingPackage + " for \"" + path + "\" with mode \"" + mode + "\""); + String callingPackageName = getCallingPackage(); + Logger.logDebug(LOG_TAG, "Open file request received from " + callingPackageName + " for \"" + path + "\" with mode \"" + mode + "\""); String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath(); // See https://support.google.com/faqs/answer/7496913: if (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) { diff --git a/termux-shared/src/main/java/com/termux/shared/data/UriUtils.java b/termux-shared/src/main/java/com/termux/shared/data/UriUtils.java new file mode 100644 index 00000000..01aa6c72 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/data/UriUtils.java @@ -0,0 +1,75 @@ +package com.termux.shared.data; + +import android.content.ContentResolver; +import android.net.Uri; + +import androidx.annotation.NonNull; + +public class UriUtils { + + /** + * Get the full file path from a {@link Uri}. + * + * If the {@link Uri} was created from file path with {@link Uri#parse(String)}, like "am" + * command "-d" option does, and the path contained a "#", then anything after it would become + * the fragment and {@link Uri#getPath()} will only return the path before it, which would be + * invalid. The fragment must be manually appended to the path to get the full path. + * + * If the {@link Uri} was created with {@link Uri.Builder} and path was set + * with {@link Uri.Builder#path(String)}, then "#" will automatically be encoded to "%23" + * and separate fragment will not exist. + * + * @param uri The {@link Uri} to get basename from. + * @return Returns the file path if found, otherwise {@code null}. + */ + public static String getUriFilePath(Uri uri) { + if (uri == null) return null; + String path = uri.getPath(); + if (DataUtils.isNullOrEmpty(path)) return null; + String fragment = uri.getFragment(); + return path + (DataUtils.isNullOrEmpty(fragment) ? "" : "#" + fragment); + } + + /** + * Get {@link ContentResolver#SCHEME_FILE} {@link Uri} for path. + * + * @param path The path for the {@link Uri}. + * @return Returns the {@link Uri}. + */ + public static Uri getFileUri(@NonNull String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_FILE).path(path).build(); + } + + /** + * Get {@link ContentResolver#SCHEME_FILE} {@link Uri} for path. + * + * @param authority The authority for the {@link Uri}. + * @param path The path for the {@link Uri}. + * @return Returns the {@link Uri}. + */ + public static Uri getFileUri(@NonNull String authority, @NonNull String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_FILE).authority(authority).path(path).build(); + } + + /** + * Get {@link ContentResolver#SCHEME_CONTENT} {@link Uri} for path. + * + * @param path The path for the {@link Uri}. + * @return Returns the {@link Uri}. + */ + public static Uri getContentUri(@NonNull String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).path(path).build(); + } + + /** + * Get {@link ContentResolver#SCHEME_CONTENT} {@link Uri} for path. + * + * @param authority The authority for the {@link Uri}. + * @param path The path for the {@link Uri}. + * @return Returns the {@link Uri}. + */ + public static Uri getContentUri(@NonNull String authority, @NonNull String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority).path(path).build(); + } + +}