/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This is re-implementation of gtk3/filetransferportal.c code.
#include "FileTransferPortal.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dlfcn.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "AsyncDBus.h"
#include "mozilla/GRefPtr.h"
#include "mozilla/GUniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
using namespace mozilla;
#undef LOGDRAG
#ifdef MOZ_LOGGING
extern mozilla::LazyLogModule gWidgetDragLog;
# define LOGDRAG(str, ...) \
MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, (str,
##__VA_ARGS__))
#else
# define LOGDRAG(...)
#endif
namespace mozilla::widget {
#ifndef O_PATH
# define O_PATH
0
#endif
#ifndef O_CLOEXEC
# define O_CLOEXEC
0
#else
# define HAVE_O_CLOEXEC
1
#endif
static constexpr
int kMaxFDsPerCall =
16;
static GUnixFDList* moz_g_unix_fd_list_new() {
static auto g_unix_fd_list_new =
(GUnixFDList * (*)()) dlsym(RTLD_DEFAULT,
"g_unix_fd_list_new");
if (g_unix_fd_list_new) {
return g_unix_fd_list_new();
}
return nullptr;
}
gint moz_g_unix_fd_list_append(GUnixFDList* list, gint fd, GError** error) {
static auto g_unix_fd_list_append =
(gint (*)(GUnixFDList* list, gint fd, GError** error))dlsym(
RTLD_DEFAULT,
"g_unix_fd_list_append");
if (g_unix_fd_list_append) {
return g_unix_fd_list_append(list, fd, error);
}
*error = nullptr;
return -
1;
}
bool FileTransferPortal::Init() {
LOGDRAG(
"FileTransferPortal::Init()");
mProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SESSION,
GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
nullptr,
"org.freedesktop.portal.Documents",
"/org/freedesktop/portal/documents",
"org.freedesktop.portal.FileTransfer", nullptr, nullptr));
if (!mProxy) {
LOGDRAG(
" missing org.freedesktop.portal.Documents!");
return false;
}
GUniquePtr<gchar> owner(g_dbus_proxy_get_name_owner(mProxy));
if (!owner) {
LOGDRAG(
" missing owner of org.freedesktop.portal.Documents!");
mProxy = nullptr;
return false;
}
g_signal_connect(
g_dbus_proxy_get_connection(mProxy),
"closed",
G_CALLBACK(+[](GDBusConnection*, gboolean, GError*, gpointer) {
FileTransferPortal::Shutdown();
}),
nullptr);
return true;
}
// Builds a GUnixFDList and variant array for a batch of files starting at
// aStart, up to kMaxFDsPerCall entries. Returns the number of files
// actually added, or an error.
static nsresult BuildFDBatch(
const nsTArray<nsCString>& aFiles,
int aStart,
GUnixFDList** aOutFDList, GVariantBuilder* aOutFDs,
int* aOutCount) {
*aOutFDList = moz_g_unix_fd_list_new();
g_variant_builder_init(aOutFDs, G_VARIANT_TYPE(
"ah"));
int count =
0;
for (
int i =
0; i < kMaxFDsPerCall && aStart + i < (
int)aFiles.Length();
i++) {
int fd = open(aFiles[aStart + i].get(), O_PATH | O_CLOEXEC);
if (fd == -
1) {
g_printerr(
"FileTransferPortal: failed to open %s: %s\n",
aFiles[aStart + i].get(), g_strerror(errno));
g_variant_builder_clear(aOutFDs);
g_object_unref(*aOutFDList);
return NS_ERROR_FILE_NOT_FOUND;
}
#ifndef HAVE_O_CLOEXEC
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
GUniquePtr<GError> error;
int fdIndex =
moz_g_unix_fd_list_append(*aOutFDList, fd, getter_Transfers(error));
close(fd);
if (fdIndex == -
1) {
g_printerr(
"FileTransferPortal: g_unix_fd_list_append failed: %s\n",
error ? error->message :
"unknown error");
g_variant_builder_clear(aOutFDs);
g_object_unref(*aOutFDList);
return NS_ERROR_FAILURE;
}
g_variant_builder_add(aOutFDs,
"h", fdIndex);
count++;
}
*aOutCount = count;
return NS_OK;
}
using AddFilesPromise = MozPromise<
bool, GUniquePtr<GError>,
true>;
RefPtr<AddFilesPromise> FileTransferPortal::AddFilesBatch(
const nsCString& aKey,
const nsTArray<nsCString>& aFiles,
int aStart) {
if (aStart >= (
int)aFiles.Length()) {
return AddFilesPromise::CreateAndResolve(
true, __func__);
}
GUnixFDList* rawFDList = nullptr;
GVariantBuilder fds;
int count =
0;
nsresult rv = BuildFDBatch(aFiles, aStart, &rawFDList, &fds, &count);
if (NS_FAILED(rv)) {
GUniquePtr<GError> error(g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to open file"));
return AddFilesPromise::CreateAndReject(std::move(error), __func__);
}
RefPtr<GUnixFDList> fdList = dont_AddRef(rawFDList);
GVariantBuilder options;
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
nsTArray<nsCString> filesCopy(aFiles.Clone());
nsCString keyCopy(aKey);
int nextStart = aStart + count;
mCancellable = GUniquePtr<GCancellable>(g_cancellable_new());
auto promise = MakeRefPtr<AddFilesPromise::
Private>(__func__);
DBusProxyCallWithUnixFDList(
mProxy,
"AddFiles",
g_variant_new(
"(saha{sv})", aKey.get(), &fds, &options),
G_DBUS_CALL_FLAGS_NONE,
/* timeout */ -1, fdList, mCancellable.get())
->Then(
GetCurrentSerialEventTarget(), __func__,
[
this, promise, keyCopy = std::move(keyCopy),
filesCopy = std::move(filesCopy),
nextStart](RefPtr<GVariant>&&)
mutable {
AddFilesBatch(keyCopy, filesCopy, nextStart)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](
bool aVal) { promise->Resolve(aVal, __func__); },
[promise](GUniquePtr<GError>&& aError) {
promise->Reject(std::move(aError), __func__);
});
},
[promise](GUniquePtr<GError>&& aError) {
promise->Reject(std::move(aError), __func__);
});
return promise;
}
RefPtr<FileTransferPortal::RegisterFilesPromise>
FileTransferPortal::RegisterFiles(
const nsTArray<nsCString>& aFiles,
bool aWritable) {
if (!mProxy) {
GUniquePtr<GError> error(g_error_new_literal(
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"No portal found"));
return RegisterFilesPromise::CreateAndReject(std::move(error), __func__);
}
GVariantBuilder options;
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&options,
"{sv}",
"writable",
g_variant_new_boolean(aWritable));
g_variant_builder_add(&options,
"{sv}",
"autostop",
g_variant_new_boolean(
TRUE));
nsTArray<nsCString> filesCopy(aFiles.Clone());
auto promise = MakeRefPtr<RegisterFilesPromise::
Private>(__func__);
mCancellable = GUniquePtr<GCancellable>(g_cancellable_new());
DBusProxyCall(mProxy,
"StartTransfer", g_variant_new(
"(a{sv})", &options),
G_DBUS_CALL_FLAGS_NONE,
/* timeout */ -1, mCancellable.get())
->Then(
GetCurrentSerialEventTarget(), __func__,
[
this, promise, filesCopy = std::move(filesCopy)](
RefPtr<GVariant>&& aResult)
mutable {
const char* key = nullptr;
g_variant_get(aResult,
"(&s)", &key);
nsCString keyCopy(key);
AddFilesBatch(keyCopy, filesCopy,
0)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, keyCopy](
bool) {
promise->Resolve(keyCopy, __func__);
},
[promise](GUniquePtr<GError>&& aError) {
promise->Reject(std::move(aError), __func__);
});
},
[promise](GUniquePtr<GError>&& aError) {
promise->Reject(std::move(aError), __func__);
});
return promise;
}
// TODO: Implement aWritable/autostop parameters?
nsresult FileTransferPortal::RegisterFilesSync(
const nsTArray<nsCString>& aFiles,
bool aWritable, nsCString& aOutKey) {
if (!mProxy) {
return NS_ERROR_NOT_AVAILABLE;
}
GVariantBuilder options;
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
GUniquePtr<GError> error;
RefPtr<GVariant> ret = dont_AddRef(g_dbus_proxy_call_sync(
mProxy,
"StartTransfer", g_variant_new(
"(a{sv})", &options),
G_DBUS_CALL_FLAGS_NONE, -
1, nullptr, getter_Transfers(error)));
if (!ret) {
g_printerr(
"FileTransferPortal::RegisterFilesSync() StartTransfer failed: %s\n",
error ? error->message :
"unknown error");
return NS_ERROR_FAILURE;
}
const char* value = nullptr;
g_variant_get(ret,
"(&s)", &value);
aOutKey = value;
int fileCount = (
int)aFiles.Length();
GUnixFDList* fdList = nullptr;
GVariantBuilder fds;
for (
int i =
0; i < fileCount; i++) {
if (!fdList) {
g_variant_builder_init(&fds, G_VARIANT_TYPE(
"ah"));
fdList = moz_g_unix_fd_list_new();
}
int fd = open(aFiles[i].get(), O_PATH | O_CLOEXEC);
if (fd == -
1) {
g_printerr(
"FileTransferPortal::RegisterFilesSync() failed to open %s: %s\n",
aFiles[i].get(), g_strerror(errno));
g_variant_builder_clear(&fds);
g_object_unref(fdList);
aOutKey.Truncate();
return NS_ERROR_FILE_NOT_FOUND;
}
#ifndef HAVE_O_CLOEXEC
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
int fdIndex =
moz_g_unix_fd_list_append(fdList, fd, getter_Transfers(error));
close(fd);
if (fdIndex == -
1) {
g_printerr(
"FileTransferPortal::RegisterFilesSync() fd_list_append failed: %s\n",
error ? error->message :
"unknown error");
g_variant_builder_clear(&fds);
g_object_unref(fdList);
aOutKey.Truncate();
return NS_ERROR_FAILURE;
}
g_variant_builder_add(&fds,
"h", fdIndex);
if ((i +
1) % kMaxFDsPerCall ==
0 || i +
1 == fileCount) {
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
ret = dont_AddRef(g_dbus_proxy_call_with_unix_fd_list_sync(
mProxy,
"AddFiles",
g_variant_new(
"(saha{sv})", aOutKey.get(), &fds, &options),
G_DBUS_CALL_FLAGS_NONE, -
1, fdList, nullptr, nullptr,
getter_Transfers(error)));
g_clear_object(&fdList);
if (!ret) {
g_printerr(
"FileTransferPortal::RegisterFilesSync() AddFiles failed: %s\n",
error ? error->message :
"unknown error");
aOutKey.Truncate();
return NS_ERROR_FAILURE;
}
}
}
return NS_OK;
}
RefPtr<FileTransferPortal::RetrieveFilesPromise>
FileTransferPortal::RetrieveFiles(
const char* aKey) {
if (!mProxy) {
GUniquePtr<GError> error(g_error_new_literal(
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"No portal found"));
return RetrieveFilesPromise::CreateAndReject(std::move(error), __func__);
}
GVariantBuilder options;
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
auto promise = MakeRefPtr<RetrieveFilesPromise::
Private>(__func__);
mCancellable = GUniquePtr<GCancellable>(g_cancellable_new());
DBusProxyCall(mProxy,
"RetrieveFiles",
g_variant_new(
"(sa{sv})", aKey, &options),
G_DBUS_CALL_FLAGS_NONE,
/* timeout */ -1, mCancellable.get())
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](RefPtr<GVariant>&& aResult) {
const char** files = nullptr;
g_variant_get(aResult,
"(^a&s)", &files);
nsTArray<nsCString> result;
if (files) {
for (
int i =
0; files[i]; i++) {
result.AppendElement(nsCString(files[i]));
}
g_free(files);
}
promise->Resolve(std::move(result), __func__);
},
[promise](GUniquePtr<GError>&& aError) {
promise->Reject(std::move(aError), __func__);
});
return promise;
}
GUniquePtr<
char*> FileTransferPortal::RetrieveFilesSync(
const char* aKey) {
if (!mProxy) {
return nullptr;
}
GVariantBuilder options;
g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
GUniquePtr<GError> error;
RefPtr<GVariant> ret = dont_AddRef(g_dbus_proxy_call_sync(
mProxy,
"RetrieveFiles", g_variant_new(
"(sa{sv})", aKey, &options),
G_DBUS_CALL_FLAGS_NONE, -
1, nullptr, getter_Transfers(error)));
if (!ret) {
g_printerr(
"FileTransferPortal::RetrieveFilesSync() failed: %s\n",
error ? error->message :
"unknown error");
return nullptr;
}
char** files = nullptr;
g_variant_get(ret,
"(^as)", &files);
return ConvertToURIs(GUniquePtr<
char*>(files));
}
GUniquePtr<
char*> FileTransferPortal::ConvertToURIs(
GUniquePtr<
char*> aFileList) {
GPtrArray* uriList = g_ptr_array_new();
for (
int i =
0; aFileList.get()[i]; i++) {
if (
auto* fileName =
g_filename_to_uri(aFileList.get()[i], nullptr, nullptr)) {
g_ptr_array_add(uriList, fileName);
}
}
g_ptr_array_add(uriList, nullptr);
return GUniquePtr<
char*>(
reinterpret_cast<
char**>(g_ptr_array_free(uriList,
false)));
}
FileTransferPortal* FileTransferPortal::sPortal = nullptr;
FileTransferPortal* FileTransferPortal::GetPortal() {
if (!sPortal) {
sPortal =
new FileTransferPortal();
if (!sPortal->Init()) {
Shutdown();
}
}
return sPortal;
}
void FileTransferPortal::Shutdown() {
delete sPortal;
sPortal = nullptr;
}
}
// namespace mozilla::widget