Correctly receive files through Android DND

* java/org/gnu/emacs/EmacsService.java (getUsefulContentResolver)
(getContentResolverContext): New functions which return a
content resolver from an EmacsActivity, if at all possible.
(openContentUri, checkContentUri): Probe or open URIs through
such content resolvers.  Probe URIs by opening them if merely
testing permissions fails, for DND URIs do not make
checkCallingUriPermission return true.

* java/org/gnu/emacs/EmacsWindow.java (onDragEvent): Address
potential crash.

* src/androidvfs.c (android_check_content_access): Circumvent
JNI dynamic method dispatch.
(android_authority_name): Guarantee NAME is never a directory.
This commit is contained in:
Po Lu 2023-10-15 13:10:34 +08:00
parent a3fd382f3f
commit 93104cff53
3 changed files with 109 additions and 9 deletions

View file

@ -921,6 +921,48 @@ invocation of app_process (through android-emacs) can
/* Content provider functions. */
/* Return a ContentResolver capable of accessing as many files as
possible, namely the content resolver of the last selected
activity if available: only they posses the rights to access drag
and drop files. */
public ContentResolver
getUsefulContentResolver ()
{
EmacsActivity activity;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
/* Since the system predates drag and drop, return this resolver
to avoid any unforseen difficulties. */
return resolver;
activity = EmacsActivity.lastFocusedActivity;
if (activity == null)
return resolver;
return activity.getContentResolver ();
}
/* Return a context whose ContentResolver is granted access to most
files, as in `getUsefulContentResolver'. */
public Context
getContentResolverContext ()
{
EmacsActivity activity;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
/* Since the system predates drag and drop, return this resolver
to avoid any unforseen difficulties. */
return this;
activity = EmacsActivity.lastFocusedActivity;
if (activity == null)
return this;
return activity;
}
/* Open a content URI described by the bytes BYTES, a non-terminated
string; make it writable if WRITABLE, and readable if READABLE.
Truncate the file if TRUNCATE.
@ -934,6 +976,9 @@ invocation of app_process (through android-emacs) can
String name, mode;
ParcelFileDescriptor fd;
int i;
ContentResolver resolver;
resolver = getUsefulContentResolver ();
/* Figure out the file access mode. */
@ -978,6 +1023,7 @@ invocation of app_process (through android-emacs) can
}
catch (Exception exception)
{
exception.printStackTrace ();
return -1;
}
}
@ -994,6 +1040,11 @@ invocation of app_process (through android-emacs) can
ParcelFileDescriptor fd;
Uri uri;
int rc, flags;
Context context;
ContentResolver resolver;
ParcelFileDescriptor descriptor;
context = getContentResolverContext ();
uri = Uri.parse (name);
flags = 0;
@ -1004,8 +1055,42 @@ invocation of app_process (through android-emacs) can
if (writable)
flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
rc = checkCallingUriPermission (uri, flags);
return rc == PackageManager.PERMISSION_GRANTED;
rc = context.checkCallingUriPermission (uri, flags);
if (rc == PackageManager.PERMISSION_GRANTED)
return true;
/* In the event checkCallingUriPermission fails and only read
permissions are being verified, attempt to query the URI. This
enables ascertaining whether drag and drop URIs can be
accessed, something otherwise not provided for. */
descriptor = null;
try
{
resolver = context.getContentResolver ();
descriptor = resolver.openFileDescriptor (uri, "r");
return true;
}
catch (Exception exception)
{
/* Ignored. */
}
finally
{
try
{
if (descriptor != null)
descriptor.close ();
}
catch (IOException exception)
{
/* Ignored. */
}
}
return false;
}
/* Build a content file name for URI.

View file

@ -1601,7 +1601,7 @@ else if (EmacsWindow.this.isMapped)
{
ClipData data;
ClipDescription description;
int i, x, y;
int i, j, x, y, itemCount;
String type;
Uri uri;
EmacsActivity activity;
@ -1626,11 +1626,12 @@ else if (EmacsWindow.this.isMapped)
data = event.getClipData ();
description = data.getDescription ();
itemCount = data.getItemCount ();
/* If there are insufficient items within the clip data,
return false. */
if (data.getItemCount () < 1)
if (itemCount < 1)
return false;
/* Search for plain text data within the clipboard. */
@ -1662,12 +1663,14 @@ else if (type.equals (ClipDescription.MIMETYPE_TEXT_URILIST))
{
/* If the item dropped is a URI, send it to the main
thread. */
uri = data.getItemAt (0).getUri ();
/* Attempt to acquire permissions for this URI;
failing which, insert it as text instead. */
if (uri.getScheme () != null
if (uri != null
&& uri.getScheme () != null
&& uri.getScheme ().equals ("content")
&& (activity = EmacsActivity.lastFocusedActivity) != null)
{

View file

@ -2898,6 +2898,7 @@ android_check_content_access (const char *uri, int mode)
{
jobject string;
jboolean rc, read, write;
jmethodID method;
string = (*android_java_env)->NewStringUTF (android_java_env, uri);
android_exception_check ();
@ -2907,11 +2908,13 @@ android_check_content_access (const char *uri, int mode)
read = (bool) (mode & R_OK || (mode == F_OK));
write = (bool) (mode & W_OK);
method = service_class.check_content_uri;
rc = (*android_java_env)->CallBooleanMethod (android_java_env,
emacs_service,
service_class.check_content_uri,
string, read, write);
rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
emacs_service,
service_class.class,
method, string, read,
write);
android_exception_check_1 (string);
ANDROID_DELETE_LOCAL_REF (string);
return rc;
@ -3013,6 +3016,15 @@ android_authority_name (struct android_vnode *vnode, char *name,
if (*name == '/')
name++, length -= 1;
/* If the provided URI is a directory, return NULL and set errno
to ENOTDIR. Content files are never directories. */
if (name[length - 1] == '/')
{
errno = ENOTDIR;
return NULL;
}
/* NAME must be a valid JNI string, so that it can be encoded
properly. */