Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement v2 notification spec #1298

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
17abcfe
icon-validator: Hard code all icon requirements in validator
jsparber Jul 1, 2024
72f0d8a
validate-icon: Read icon from fd instead of a file
jsparber Jul 2, 2024
841d6dc
validate-icon: Add file limit of 4MB
jsparber Jul 4, 2024
f80afc1
xdp-utils: Use memfd_create() instead of temporary file
jsparber Jul 2, 2024
edde298
notification: Bump interface to version 2
jsparber Mar 8, 2024
170f749
notification: Accept icons as file descriptor
jsparber Jan 18, 2024
209285a
tests: Implement tests for file-descriptor notification icons
jsparber Feb 27, 2024
16b3e0c
notification: Add new sound property
jsparber Mar 5, 2024
4c0b998
test: Implement tests for notification sound property
jsparber Mar 7, 2024
76d4c51
notification: Add new property markup-body to support markup
jsparber Mar 22, 2024
4e56fdd
notification: Improve description of title and body
jsparber Apr 12, 2024
852dce9
test: Implement test for notification property markup-body
jsparber Mar 22, 2024
9cd8168
notification: Add new property desktop-file-id
jsparber Mar 19, 2024
56c5bdb
test: Implement tests for desktop-file-id notification property
jsparber Mar 22, 2024
d02de8b
notification: Add display-hint property
jsparber Mar 21, 2024
5595c5d
test: Implement tests for display-hint notification property
jsparber Mar 22, 2024
be0e397
notification: Improve wording around DBus Activation
jsparber Apr 16, 2024
596a7f3
notification: Add platform-data to ActionInvoked signal
jsparber Apr 16, 2024
756dd90
notification: Add category property
jsparber Mar 21, 2024
5ede221
notification: Add purpose property for buttons
jsparber Mar 21, 2024
a28bab7
test: Implement tests for category notification property
jsparber Mar 22, 2024
5f415af
notification: Add supported-options property
jsparber Mar 21, 2024
2b87e09
test: Implement tests for supported-options property in notification
jsparber Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ endif
xdp_validate_icon = executable(
'xdg-desktop-portal-validate-icon',
'validate-icon.c',
dependencies: [gdk_pixbuf_dep],
dependencies: [gdk_pixbuf_dep, gio_unix_dep],
c_args: validate_icon_c_args,
install: true,
install_dir: libexecdir,
Expand Down
104 changes: 51 additions & 53 deletions src/validate-icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*
* Authors:
* Matthias Clasen <[email protected]>
* Julian Sparber <[email protected]>
*/

/* The canonical copy of this file is in:
Expand All @@ -26,6 +27,7 @@

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glib/gstdio.h>
#include <gio/gunixinputstream.h>
#include <errno.h>
#include <unistd.h>

Expand All @@ -34,81 +36,80 @@
#endif

#define ICON_VALIDATOR_GROUP "Icon Validator"
#define MAX_ICON_SIZE 512
#define MAX_SVG_ICON_SIZE 4096
#define BUFFER_SIZE 4096
#define INPUT_FD 3

static int
validate_icon (const char *arg_width,
const char *arg_height,
const char *filename)
validate_icon ()
{
GdkPixbufFormat *format;
int max_width, max_height;
int width, height;
g_autofree char *name = NULL;
const char *allowed_formats[] = { "png", "jpeg", "svg", NULL };
g_autoptr(GdkPixbuf) pixbuf = NULL;
g_autoptr(GBytes) bytes = NULL;
g_autoptr(GError) error = NULL;
GdkPixbufFormat *format;
g_autoptr(GKeyFile) key_file = NULL;
g_autofree char *key_file_data = NULL;
g_autoptr(GdkPixbufLoader) loader = NULL;
g_autoptr(GMappedFile) mapped = NULL;
int max_size, width, height;
g_autofree char *name = NULL;
GdkPixbuf *pixbuf;

format = gdk_pixbuf_get_file_info (filename, &width, &height);
if (format == NULL)
if (!(mapped = g_mapped_file_new_from_fd (INPUT_FD, FALSE, &error)))
{
g_printerr ("Format not recognized\n");
g_printerr ("Failed to create mapped file for image: %s\n", error->message);
return 1;
}

name = gdk_pixbuf_format_get_name (format);
if (!g_strv_contains (allowed_formats, name))
bytes = g_mapped_file_get_bytes (mapped);

loader = gdk_pixbuf_loader_new ();

if (!gdk_pixbuf_loader_write_bytes (loader, bytes, &error) ||
!gdk_pixbuf_loader_close (loader, &error) ||
!(pixbuf = gdk_pixbuf_loader_get_pixbuf (loader)))
{
g_printerr ("Format %s not accepted\n", name);
g_printerr ("Failed to load image: %s\n", error->message);
gdk_pixbuf_loader_close (loader, NULL);
return 1;
}

if (!g_str_equal (name, "svg"))
if (!(format = gdk_pixbuf_loader_get_format (loader)))
{
max_width = g_ascii_strtoll (arg_width, NULL, 10);
if (max_width < 16 || max_width > 4096)
{
g_printerr ("Bad width limit: %s\n", arg_width);
return 1;
}

max_height = g_ascii_strtoll (arg_height, NULL, 10);
if (max_height < 16 || max_height > 4096)
{
g_printerr ("Bad height limit: %s\n", arg_height);
return 1;
}
}
else
{
/* Sanity check for vector files */
max_height = max_width = 4096;
g_printerr ("Image format not recognized\n");
return 1;
}

if (width > max_width || height > max_height)
name = gdk_pixbuf_format_get_name (format);
if (!g_strv_contains (allowed_formats, name))
{
g_printerr ("Image too large (%dx%d). Max. size %dx%d\n", width, height, max_width, max_height);
g_printerr ("Image format %s not accepted\n", name);
return 1;
}

pixbuf = gdk_pixbuf_new_from_file (filename, &error);
if (pixbuf == NULL)
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);

if (width != height)
{
g_printerr ("Failed to load image: %s\n", error->message);
g_printerr ("Expected a square image but got: %dx%d\n", width, height);
return 1;
}

if (width != height)
/* Sanity check for vector files */
max_size = g_str_equal (name, "svg") ? MAX_SVG_ICON_SIZE : MAX_ICON_SIZE;

/* The icon is a square so we only need to check one side */
if (width > max_size)
{
g_printerr ("Expected a square icon but got: %dx%d\n", width, height);
g_printerr ("Image too large (%dx%d). Max. size %dx%d\n", width, height, max_size, max_size);
return 1;
}

/* Print the format and size for consumption by (at least) the dynamic
* launcher portal. xdg-desktop-portal has a copy of this file. Use a
* GKeyFile so the output can be easily extended in the future in a backwards
* compatible way.
* launcher portal. Use a GKeyFile so the output can be easily extended
* in the future in a backwards compatible way.
*/
key_file = g_key_file_new ();
g_key_file_set_string (key_file, ICON_VALIDATOR_GROUP, "format", name);
Expand Down Expand Up @@ -165,9 +166,7 @@ flatpak_get_bwrap (void)
}

static int
rerun_in_sandbox (const char *arg_width,
const char *arg_height,
const char *filename)
rerun_in_sandbox (void)
{
const char * const usrmerged_dirs[] = { "bin", "lib32", "lib64", "lib", "sbin" };
int i;
Expand Down Expand Up @@ -226,15 +225,14 @@ rerun_in_sandbox (const char *arg_width,
"--setenv", "GIO_USE_VFS", "local",
"--unsetenv", "TMPDIR",
"--die-with-parent",
"--ro-bind", filename, filename,
NULL);

if (g_getenv ("G_MESSAGES_DEBUG"))
add_args (args, "--setenv", "G_MESSAGES_DEBUG", g_getenv ("G_MESSAGES_DEBUG"), NULL);
if (g_getenv ("G_MESSAGES_PREFIXED"))
add_args (args, "--setenv", "G_MESSAGES_PREFIXED", g_getenv ("G_MESSAGES_PREFIXED"), NULL);

add_args (args, validate_icon, arg_width, arg_height, filename, NULL);
add_args (args, validate_icon, NULL);
g_ptr_array_add (args, NULL);

execvpe (flatpak_get_bwrap (), (char **) args->pdata, NULL);
Expand All @@ -257,24 +255,24 @@ main (int argc, char *argv[])
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) error = NULL;

context = g_option_context_new ("WIDTH HEIGHT PATH");
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}

if (argc != 4)
if (argc != 1)
{
g_printerr ("Usage: %s [OPTION…] WIDTH HEIGHT PATH\n", argv[0]);
g_printerr ("Usage: %s [OPTION…]\n", argv[0]);
return 1;
}

#ifdef HELPER
if (opt_sandbox)
return rerun_in_sandbox (argv[1], argv[2], argv[3]);
return rerun_in_sandbox ();
else
#endif
return validate_icon (argv[1], argv[2], argv[3]);
return validate_icon ();
}
104 changes: 76 additions & 28 deletions src/xdp-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#include <gio/gio.h>
#include <gio/gunixoutputstream.h>
Expand Down Expand Up @@ -572,6 +573,75 @@ cleanup_temp_file (void *p)
g_free (*pp);
}

#define VALIDATOR_INPUT_FD 3

static GKeyFile *
spawn_validator (const char* const* args,
int fd)
{
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) bytes_output = NULL;
g_autoptr(GKeyFile) key_file = NULL;
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GMappedFile) mapped_output = NULL;
g_autoptr(GSubprocess) process = NULL;
xdp_autofd int input_fd = -1;
int output_fd = -1;
xdp_autofd int stdout_fd = -1;

stdout_fd = memfd_create("spawn-validator-stdout", 0);
jsparber marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point of using a memfd for stdout. It's a few bytes of text.


if (stdout_fd == -1)
{
int saved_errno;

saved_errno = errno;
g_warning ("Failed to spawn subprocess: memfd_create: %s", g_strerror (saved_errno));
jsparber marked this conversation as resolved.
Show resolved Hide resolved
return NULL;
}

input_fd = dup(fd);
// We don't need to dup() it since it will life till the launcher is dropped
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong comment style

output_fd = stdout_fd;

/* Ensure that we read from the beginning of the file */
lseek(input_fd, 0, SEEK_SET);

launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
g_subprocess_launcher_take_stdout_fd (launcher, xdp_steal_fd (&stdout_fd));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is transferring ownership. At that point using the fd (output_fd or stdout_fd) is an error! If you want to be able to use the fd while the function takes ownership, you have to dup the fd.

Nothing here is crashing because the launcher keeps the fd around until it is disposed via the autoptr but conceptually, the launcher owns the fd and could close it at any time.

g_subprocess_launcher_take_fd (launcher, xdp_steal_fd (&input_fd), VALIDATOR_INPUT_FD);
process = g_subprocess_launcher_spawnv (launcher, args, &error);

if (!process)
{
g_warning ("Failed to spawn subprocess: %s", error->message);
jsparber marked this conversation as resolved.
Show resolved Hide resolved
return NULL;
}

if (!g_subprocess_wait_check (process, NULL, &error))
{
/* The child's stderr is automatically redirected to stderr, therefore no need to log it here. */
g_warning ("Failed to spawn subprocess: %s", error->message);
return NULL;
}

if (!(mapped_output = g_mapped_file_new_from_fd (output_fd, FALSE, &error)))
{
g_warning ("Failed to get output from subprocess: %s", error->message);
return NULL;
}

bytes_output = g_mapped_file_get_bytes (mapped_output);

key_file = g_key_file_new ();
if (!g_key_file_load_from_bytes (key_file, bytes_output, G_KEY_FILE_NONE, &error))
{
g_warning ("Icon validation: %s", error->message);
return NULL;
}
return g_steal_pointer (&key_file);
}

#define ICON_VALIDATOR_GROUP "Icon Validator"

gboolean
Expand All @@ -585,17 +655,11 @@ xdp_validate_serialized_icon (GVariant *v,
__attribute__((cleanup(cleanup_temp_file))) char *name = NULL;
xdp_autofd int fd = -1;
g_autoptr(GOutputStream) stream = NULL;
int status;
g_autofree char *format = NULL;
g_autofree char *stdoutlog = NULL;
g_autofree char *stderrlog = NULL;
g_autoptr(GError) error = NULL;
const char *icon_validator = LIBEXECDIR "/xdg-desktop-portal-validate-icon";
const char *args[6];
/* same allowed formats as Flatpak */
const char *allowed_icon_formats[] = { "png", "jpeg", "svg", NULL };
const char *args[3];
int size;
const char *MAX_ICON_SIZE = "512";
gconstpointer bytes_data;
gsize bytes_len;
g_autoptr(GKeyFile) key_file = NULL;
Expand Down Expand Up @@ -657,37 +721,21 @@ xdp_validate_serialized_icon (GVariant *v,

args[0] = icon_validator;
args[1] = "--sandbox";
args[2] = MAX_ICON_SIZE;
args[3] = MAX_ICON_SIZE;
args[4] = name;
args[5] = NULL;
args[2] = NULL;

if (!g_spawn_sync (NULL, (char **)args, NULL, 0, NULL, NULL, &stdoutlog, &stderrlog, &status, &error))
{
g_warning ("Icon validation: %s", error->message);
g_warning ("stderr:\n%s\n", stderrlog);
return FALSE;
}
key_file = spawn_validator (args, fd);

if (!g_spawn_check_exit_status (status, &error))
if (!key_file)
{
g_warning ("Icon validation: %s", error->message);
g_warning ("stderr:\n%s\n", stderrlog);
g_warning ("Icon validation: Rejecting icon because validator process failed");
return FALSE;
}

key_file = g_key_file_new ();
if (!g_key_file_load_from_data (key_file, stdoutlog, -1, G_KEY_FILE_NONE, &error))
if (!(format = g_key_file_get_string (key_file, ICON_VALIDATOR_GROUP, "format", &error)))
{
g_warning ("Icon validation: %s", error->message);
return FALSE;
}
if (!(format = g_key_file_get_string (key_file, ICON_VALIDATOR_GROUP, "format", &error)) ||
!g_strv_contains (allowed_icon_formats, format))
{
g_warning ("Icon validation: %s", error ? error->message : "not allowed format");
return FALSE;
}
if (!(size = g_key_file_get_integer (key_file, ICON_VALIDATOR_GROUP, "width", &error)))
{
g_warning ("Icon validation: %s", error->message);
Expand Down