Update nativefiledialog-extended to 3311592.

This commit is contained in:
Bartosz Taudul 2022-07-17 16:22:27 +02:00
parent 85102f7a19
commit d6a607b9f0
No known key found for this signature in database
GPG Key ID: B7FE2008B7575DF3
2 changed files with 141 additions and 79 deletions

View File

@ -9,17 +9,23 @@
#include <Availability.h> #include <Availability.h>
#include "nfd.h" #include "nfd.h"
// At least one of NFD_NEEDS_ALLOWEDCONTENTTYPES and NFD_NEEDS_ALLOWEDFILETYPES will be defined // MacOS is deprecating the allowedFileTypes property in favour of allowedContentTypes, so we have
#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && defined(__MAC_12_0) && \ // to introduce this breaking change. Define NFD_MACOS_ALLOWEDCONTENTTYPES to 1 to have it set the
__MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_12_0 // allowedContentTypes property of the SavePanel or OpenPanel. Define
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h> // NFD_MACOS_ALLOWEDCONTENTTYPES to 0 to have it set the allowedFileTypes property of the SavePanel
#define NFD_NEEDS_ALLOWEDCONTENTTYPES // or OpenPanel. If NFD_MACOS_ALLOWEDCONTENTTYPES is undefined, then it will set it to 1 if
#if !defined(__MAC_OS_X_VERSION_MIN_ALLOWED) || !defined(__MAC_12_0) || \ // __MAC_OS_X_VERSION_MIN_REQUIRED >= 11.0, and 0 otherwise.
__MAC_OS_X_VERSION_MIN_ALLOWED < __MAC_12_0 #if !defined(NFD_MACOS_ALLOWEDCONTENTTYPES)
#define NFD_NEEDS_ALLOWEDFILETYPES #if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(__MAC_11_0) || \
#endif __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_11_0
#define NFD_MACOS_ALLOWEDCONTENTTYPES 0
#else #else
#define NFD_NEEDS_ALLOWEDFILETYPES #define NFD_MACOS_ALLOWEDCONTENTTYPES 1
#endif
#endif
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#endif #endif
static const char* g_errorstr = NULL; static const char* g_errorstr = NULL;
@ -40,7 +46,7 @@ static void NFDi_Free(void* ptr) {
free(ptr); free(ptr);
} }
#if defined(NFD_NEEDS_ALLOWEDCONTENTTYPES) #if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
// Returns an NSArray of UTType representing the content types. // Returns an NSArray of UTType representing the content types.
static NSArray* BuildAllowedContentTypes(const nfdnfilteritem_t* filterList, static NSArray* BuildAllowedContentTypes(const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) { nfdfiltersize_t filterCount) {
@ -81,9 +87,7 @@ static NSArray* BuildAllowedContentTypes(const nfdnfilteritem_t* filterList,
return returnArray; return returnArray;
} }
#endif #else
#if defined(NFD_NEEDS_ALLOWEDFILETYPES)
// Returns an NSArray of NSString representing the file types. // Returns an NSArray of NSString representing the file types.
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList, static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) { nfdfiltersize_t filterCount) {
@ -130,21 +134,8 @@ static void AddFilterListToDialog(NSSavePanel* dialog,
assert(filterList); assert(filterList);
// Make NSArray of file types and set it on the dialog // Make NSArray of file types and set it on the dialog
// We use setAllowedFileTypes or setAllowedContentTypes depending on the OS version // We use setAllowedFileTypes or setAllowedContentTypes depending on the deployment target
#if defined(NFD_NEEDS_ALLOWEDCONTENTTYPES) && defined(NFD_NEEDS_ALLOWEDFILETYPES) #if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
// If both are needed, it means we have to do a runtime check
if (@available(macOS 12.0, *)) {
NSArray* allowedContentTypes = BuildAllowedContentTypes(filterList, filterCount);
[dialog setAllowedContentTypes:allowedContentTypes];
} else {
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
// Unfortunately @available doesn't silence deprecation warnings so we need these pragmas
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[dialog setAllowedFileTypes:allowedFileTypes];
#pragma clang diagnostic pop
}
#elif defined(NFD_NEEDS_ALLOWEDCONTENTTYPES)
NSArray* allowedContentTypes = BuildAllowedContentTypes(filterList, filterCount); NSArray* allowedContentTypes = BuildAllowedContentTypes(filterList, filterCount);
[dialog setAllowedContentTypes:allowedContentTypes]; [dialog setAllowedContentTypes:allowedContentTypes];
#else #else

View File

@ -20,13 +20,12 @@
#include "nfd.h" #include "nfd.h"
/* /*
Define NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION to 0 if you don't want the file extension to be Define NFD_APPEND_EXTENSION if you want the file extension to be appended when missing. Linux
appended when missing. Linux programs usually doesn't append the file extension, but for consistency programs usually don't append the file extension, but for consistency with other OSes you might want
with other OSes we append it by default. to append it. However, when using portals, the file overwrite prompt and the Flatpak sandbox won't
know that we appended an extension, so they will not check or whitelist the correct file. Enabling
NFD_APPEND_EXTENSION is not recommended for portals.
*/ */
#ifndef NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION
#define NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION 1
#endif
namespace { namespace {
@ -720,14 +719,14 @@ nfdresult_t ReadResponseUrisSingle(DBusMessage* msg, const char*& file) {
const nfdresult_t res = ReadResponseUris(msg, uri_iter); const nfdresult_t res = ReadResponseUris(msg, uri_iter);
if (res != NFD_OKAY) return res; // can be NFD_CANCEL or NFD_ERROR if (res != NFD_OKAY) return res; // can be NFD_CANCEL or NFD_ERROR
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) { if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
NFDi_SetError("D-Bus response signal URI sub iter is not an string."); NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_ERROR; return NFD_ERROR;
} }
dbus_message_iter_get_basic(&uri_iter, &file); dbus_message_iter_get_basic(&uri_iter, &file);
return NFD_OKAY; return NFD_OKAY;
} }
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1 #ifdef NFD_APPEND_EXTENSION
// Read the response URI and selected extension (in the form "*.abc" or "*") (if any). If response // Read the response URI and selected extension (in the form "*.abc" or "*") (if any). If response
// was okay, then returns NFD_OKAY and set file and extn to them (the pointer is set to some string // was okay, then returns NFD_OKAY and set file and extn to them (the pointer is set to some string
// owned by msg, so you should not manually free it). `file` is the user-entered file name, and // owned by msg, so you should not manually free it). `file` is the user-entered file name, and
@ -775,7 +774,7 @@ nfdresult_t ReadResponseUrisSingleAndCurrentExtension(DBusMessage* msg,
} }
if (dbus_message_iter_get_arg_type(&current_filter_struct_iter) != if (dbus_message_iter_get_arg_type(&current_filter_struct_iter) !=
DBUS_TYPE_ARRAY) { DBUS_TYPE_ARRAY) {
// NFDi_SetError("D-Bus response signal URI sub iter is not an string."); // NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_OKAY; return NFD_OKAY;
} }
DBusMessageIter current_filter_array_iter; DBusMessageIter current_filter_array_iter;
@ -788,7 +787,7 @@ nfdresult_t ReadResponseUrisSingleAndCurrentExtension(DBusMessage* msg,
DBusMessageIter current_filter_extn_iter; DBusMessageIter current_filter_extn_iter;
dbus_message_iter_recurse(&current_filter_array_iter, &current_filter_extn_iter); dbus_message_iter_recurse(&current_filter_array_iter, &current_filter_extn_iter);
if (dbus_message_iter_get_arg_type(&current_filter_extn_iter) != DBUS_TYPE_UINT32) { if (dbus_message_iter_get_arg_type(&current_filter_extn_iter) != DBUS_TYPE_UINT32) {
// NFDi_SetError("D-Bus response signal URI sub iter is not an string."); // NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_OKAY; return NFD_OKAY;
} }
dbus_uint32_t type; dbus_uint32_t type;
@ -803,7 +802,7 @@ nfdresult_t ReadResponseUrisSingleAndCurrentExtension(DBusMessage* msg,
return NFD_OKAY; return NFD_OKAY;
} }
if (dbus_message_iter_get_arg_type(&current_filter_extn_iter) != DBUS_TYPE_STRING) { if (dbus_message_iter_get_arg_type(&current_filter_extn_iter) != DBUS_TYPE_STRING) {
// NFDi_SetError("D-Bus response signal URI sub iter is not an string."); // NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_OKAY; return NFD_OKAY;
} }
dbus_message_iter_get_basic(&current_filter_extn_iter, &tmp_extn); dbus_message_iter_get_basic(&current_filter_extn_iter, &tmp_extn);
@ -942,12 +941,69 @@ class DBusSignalSubscriptionHandler {
} }
}; };
// Returns true if ch is in [0-9A-Za-z], false otherwise.
bool IsHex(char ch) {
return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F') || ('a' <= ch && ch <= 'f');
}
// Returns the hexadecimal value contained in the char. Precondition: IsHex(ch)
char ParseHexUnchecked(char ch) {
if ('0' <= ch && ch <= '9') return ch - '0';
if ('A' <= ch && ch <= 'F') return ch - ('A' - 10);
if ('a' <= ch && ch <= 'f') return ch - ('a' - 10);
#if defined(__GNUC__)
__builtin_unreachable();
#endif
}
// Returns true if the given file URI is decodable (i.e. not malformed), and false otherwise.
// If this function returns true, then `out` will be populated with the length of the decoded URI
// and `fileUriEnd` will point to the trailing null byte of `fileUri`. Otherwise, `out` and
// `fileUriEnd` will be unmodified.
bool TryUriDecodeLen(const char* fileUri, size_t& out, const char*& fileUriEnd) {
size_t len = 0;
while (*fileUri) {
if (*fileUri != '%') {
++fileUri;
} else {
if (*(fileUri + 1) == '\0' || *(fileUri + 2) == '\0') {
return false;
}
if (!IsHex(*(fileUri + 1)) || !IsHex(*(fileUri + 2))) {
return false;
}
fileUri += 3;
}
++len;
}
out = len;
fileUriEnd = fileUri;
return true;
}
// Decodes the given URI and writes it to `outPath`. The caller must ensure that the given URI is
// not malformed (typically with a prior call to `TryUriDecodeLen`). This function does not write
// any trailing null character.
char* UriDecodeUnchecked(const char* fileUri, const char* fileUriEnd, char* outPath) {
while (fileUri != fileUriEnd) {
if (*fileUri != '%') {
*outPath++ = *fileUri++;
} else {
++fileUri;
const char high_nibble = ParseHexUnchecked(*fileUri++);
const char low_nibble = ParseHexUnchecked(*fileUri++);
*outPath++ = (high_nibble << 4) | low_nibble;
}
}
return outPath;
}
constexpr const char FILE_URI_PREFIX[] = "file://"; constexpr const char FILE_URI_PREFIX[] = "file://";
constexpr size_t FILE_URI_PREFIX_LEN = sizeof(FILE_URI_PREFIX) - 1; constexpr size_t FILE_URI_PREFIX_LEN = sizeof(FILE_URI_PREFIX) - 1;
// If fileUri starts with "file://", strips that prefix and copies it to a new buffer, and make // If fileUri starts with "file://", strips that prefix and URI-decodes the remaining part to a new
// outPath point to it, and returns NFD_OKAY. Otherwise, does not modify outPath and returns // buffer, and make outPath point to it, and returns NFD_OKAY. Otherwise, does not modify outPath
// NFD_ERROR (with the correct error set) // and returns NFD_ERROR (with the correct error set)
nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) { nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) {
const char* prefix_begin = FILE_URI_PREFIX; const char* prefix_begin = FILE_URI_PREFIX;
const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN; const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN;
@ -957,14 +1013,20 @@ nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) {
return NFD_ERROR; return NFD_ERROR;
} }
} }
size_t len = strlen(fileUri); size_t decoded_len;
char* path_without_prefix = NFDi_Malloc<char>(len + 1); const char* file_uri_end;
copy(fileUri, fileUri + (len + 1), path_without_prefix); if (!TryUriDecodeLen(fileUri, decoded_len, file_uri_end)) {
NFDi_SetError("D-Bus freedesktop portal returned a malformed URI.");
return NFD_ERROR;
}
char* const path_without_prefix = NFDi_Malloc<char>(decoded_len + 1);
char* const out_end = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix);
*out_end = '\0';
outPath = path_without_prefix; outPath = path_without_prefix;
return NFD_OKAY; return NFD_OKAY;
} }
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1 #ifdef NFD_APPEND_EXTENSION
bool TryGetValidExtension(const char* extn, bool TryGetValidExtension(const char* extn,
const char*& trimmed_extn, const char*& trimmed_extn,
const char*& trimmed_extn_end) { const char*& trimmed_extn_end) {
@ -994,10 +1056,18 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn,
} }
} }
const char* file_end = fileUri; size_t decoded_len;
for (; *file_end != '\0'; ++file_end) const char* file_uri_end;
; if (!TryUriDecodeLen(fileUri, decoded_len, file_uri_end)) {
const char* file_it = file_end; NFDi_SetError("D-Bus freedesktop portal returned a malformed URI.");
return NFD_ERROR;
}
const char* file_it = file_uri_end;
// The following loop condition is safe because `FILE_URI_PREFIX` ends with '/',
// so we won't iterate past the beginning of the URI.
// Also in UTF-8 all non-ASCII code points are encoded using bytes 128-255 so every '.' or '/'
// is also '.' or '/' in UTF-8.
do { do {
--file_it; --file_it;
} while (*file_it != '/' && *file_it != '.'); } while (*file_it != '/' && *file_it != '.');
@ -1005,16 +1075,17 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn,
const char* trimmed_extn_end; // includes the '\0' const char* trimmed_extn_end; // includes the '\0'
if (*file_it == '.' || !TryGetValidExtension(extn, trimmed_extn, trimmed_extn_end)) { if (*file_it == '.' || !TryGetValidExtension(extn, trimmed_extn, trimmed_extn_end)) {
// has file extension already or no valid extension in `extn` // has file extension already or no valid extension in `extn`
++file_end; // includes the '\0' char* const path_without_prefix = NFDi_Malloc<char>(decoded_len + 1);
char* path_without_prefix = NFDi_Malloc<char>(file_end - fileUri); char* const out_end = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix);
copy(fileUri, file_end, path_without_prefix); *out_end = '\0';
outPath = path_without_prefix; outPath = path_without_prefix;
} else { } else {
// no file extension and we have a valid extension // no file extension and we have a valid extension
char* path_without_prefix = char* const path_without_prefix =
NFDi_Malloc<char>((file_end - fileUri) + (trimmed_extn_end - trimmed_extn)); NFDi_Malloc<char>(decoded_len + (trimmed_extn_end - trimmed_extn));
char* out = copy(fileUri, file_end, path_without_prefix); char* const out_mid = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix);
copy(trimmed_extn, trimmed_extn_end, out); char* const out_end = copy(trimmed_extn, trimmed_extn_end, out_mid);
*out_end = '\0';
outPath = path_without_prefix; outPath = path_without_prefix;
} }
return NFD_OKAY; return NFD_OKAY;
@ -1244,15 +1315,15 @@ nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
} }
DBusMessage_Guard msg_guard(msg); DBusMessage_Guard msg_guard(msg);
const char* file; const char* uri;
{ {
const nfdresult_t res = ReadResponseUrisSingle(msg, file); const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
if (res != NFD_OKAY) { if (res != NFD_OKAY) {
return res; return res;
} }
} }
return AllocAndCopyFilePath(file, *outPath); return AllocAndCopyFilePath(uri, *outPath);
} }
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
@ -1295,27 +1366,27 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
} }
DBusMessage_Guard msg_guard(msg); DBusMessage_Guard msg_guard(msg);
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1 #ifdef NFD_APPEND_EXTENSION
const char* file; const char* uri;
const char* extn; const char* extn;
{ {
const nfdresult_t res = ReadResponseUrisSingleAndCurrentExtension(msg, file, extn); const nfdresult_t res = ReadResponseUrisSingleAndCurrentExtension(msg, uri, extn);
if (res != NFD_OKAY) { if (res != NFD_OKAY) {
return res; return res;
} }
} }
return AllocAndCopyFilePathWithExtn(file, extn, *outPath); return AllocAndCopyFilePathWithExtn(uri, extn, *outPath);
#else #else
const char* file; const char* uri;
{ {
const nfdresult_t res = ReadResponseUrisSingle(msg, file); const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
if (res != NFD_OKAY) { if (res != NFD_OKAY) {
return res; return res;
} }
} }
return AllocAndCopyFilePath(file, *outPath); return AllocAndCopyFilePath(uri, *outPath);
#endif #endif
} }
@ -1331,15 +1402,15 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath)
} }
DBusMessage_Guard msg_guard(msg); DBusMessage_Guard msg_guard(msg);
const char* file; const char* uri;
{ {
const nfdresult_t res = ReadResponseUrisSingle(msg, file); const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
if (res != NFD_OKAY) { if (res != NFD_OKAY) {
return res; return res;
} }
} }
return AllocAndCopyFilePath(file, *outPath); return AllocAndCopyFilePath(uri, *outPath);
} }
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) { nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
@ -1364,12 +1435,12 @@ nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
} }
} }
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) { if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
NFDi_SetError("D-Bus response signal URI sub iter is not an string."); NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_ERROR; return NFD_ERROR;
} }
const char* file; const char* uri;
dbus_message_iter_get_basic(&uri_iter, &file); dbus_message_iter_get_basic(&uri_iter, &uri);
return AllocAndCopyFilePath(file, *outPath); return AllocAndCopyFilePath(uri, *outPath);
} }
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) { void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) {
@ -1402,12 +1473,12 @@ nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** out
return NFD_OKAY; return NFD_OKAY;
} }
if (arg_type != DBUS_TYPE_STRING) { if (arg_type != DBUS_TYPE_STRING) {
NFDi_SetError("D-Bus response signal URI sub iter is not an string."); NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
return NFD_ERROR; return NFD_ERROR;
} }
const char* file; const char* uri;
dbus_message_iter_get_basic(&uri_iter, &file); dbus_message_iter_get_basic(&uri_iter, &uri);
const nfdresult_t res = AllocAndCopyFilePath(file, *outPath); const nfdresult_t res = AllocAndCopyFilePath(uri, *outPath);
if (res != NFD_OKAY) return res; if (res != NFD_OKAY) return res;
dbus_message_iter_next(&uri_iter); dbus_message_iter_next(&uri_iter);
return NFD_OKAY; return NFD_OKAY;