aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkj_sh6042026-05-26 20:38:05 -0400
committerkj_sh6042026-05-26 20:38:05 -0400
commit64b2194b38f5bb7891c5a58f425d5814536d3343 (patch)
tree701595865166c42a3534d7e6c8c61b199484e310
parenta71dbc43e71da93784d09db5fa37ae7c103a6ead (diff)
refactor: memory management and security changes
also add navigation code comments
-rw-r--r--src/dapp.c155
1 files changed, 139 insertions, 16 deletions
diff --git a/src/dapp.c b/src/dapp.c
index 6d7d651..a277d1d 100644
--- a/src/dapp.c
+++ b/src/dapp.c
@@ -1,5 +1,6 @@
#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
#include <gio/gdesktopappinfo.h>
#include <gtk/gtk.h>
#include <signal.h>
@@ -17,11 +18,36 @@ typedef struct {
typedef struct {
GPtrArray *entries;
+ GHashTable *seen_paths;
GtkWidget *window;
GtkWidget *list;
GtkWidget *status_label;
} LauncherState;
+// nav: limits
+enum {
+ MAX_INPUT_LINE_BYTES = 4096,
+ MAX_DESKTOP_ENTRIES = 4096,
+ MAX_APP_NAME_CHARS = 160
+};
+
+// nav: state cleanup
+static void launcher_state_cleanup(LauncherState *state) {
+ if (state == NULL) {
+ return;
+ }
+
+ if (state->entries != NULL) {
+ g_ptr_array_free(state->entries, TRUE);
+ state->entries = NULL;
+ }
+
+ if (state->seen_paths != NULL) {
+ g_hash_table_destroy(state->seen_paths);
+ state->seen_paths = NULL;
+ }
+}
+
static void app_entry_free(gpointer data) {
AppEntry *entry = (AppEntry *)data;
if (entry == NULL) {
@@ -55,6 +81,7 @@ static char *trim_in_place(char *text) {
return start;
}
+// nav: path helpers
static char *expand_path(const char *path) {
if (path == NULL || *path == '\0') {
return NULL;
@@ -74,18 +101,80 @@ static char *expand_path(const char *path) {
return g_strdup(path);
}
+static char *normalize_desktop_path(const char *raw_path) {
+ char *expanded = NULL;
+ char *canonical = NULL;
+
+ expanded = expand_path(raw_path);
+ if (expanded == NULL) {
+ return NULL;
+ }
+
+ canonical = g_canonicalize_filename(expanded, NULL);
+ g_free(expanded);
+
+ if (canonical == NULL || !g_path_is_absolute(canonical)) {
+ g_free(canonical);
+ return NULL;
+ }
+
+ return canonical;
+}
+
+static char *build_safe_display_name(GDesktopAppInfo *app, const char *fallback_path) {
+ const char *name = g_app_info_get_display_name(G_APP_INFO(app));
+ char *safe_name = NULL;
+
+ if (name == NULL || *name == '\0') {
+ name = g_app_info_get_name(G_APP_INFO(app));
+ }
+
+ if (name == NULL || *name == '\0') {
+ name = fallback_path;
+ }
+
+ safe_name = g_utf8_make_valid(name != NULL ? name : "app", -1);
+ if (safe_name == NULL || *safe_name == '\0') {
+ g_free(safe_name);
+ safe_name = g_strdup("app");
+ }
+
+ if (g_utf8_strlen(safe_name, -1) > MAX_APP_NAME_CHARS) {
+ char *truncated = g_utf8_substring(safe_name, 0, MAX_APP_NAME_CHARS);
+ g_free(safe_name);
+ safe_name = truncated;
+ }
+
+ return safe_name;
+}
+
+// nav: desktop entry loading
static gboolean add_desktop_path(LauncherState *state, const char *raw_path) {
char *path = NULL;
GDesktopAppInfo *app = NULL;
- const char *display_name = NULL;
+ char *safe_name = NULL;
AppEntry *entry = NULL;
- path = expand_path(raw_path);
+ if (state->entries->len >= MAX_DESKTOP_ENTRIES) {
+ return FALSE;
+ }
+
+ path = normalize_desktop_path(raw_path);
if (path == NULL) {
return FALSE;
}
- if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) {
+ if (!g_str_has_suffix(path, ".desktop")) {
+ g_free(path);
+ return FALSE;
+ }
+
+ if (g_hash_table_contains(state->seen_paths, path)) {
+ g_free(path);
+ return FALSE;
+ }
+
+ if (!g_file_test(path, G_FILE_TEST_IS_REGULAR) || access(path, R_OK) != 0) {
g_free(path);
return FALSE;
}
@@ -96,32 +185,47 @@ static gboolean add_desktop_path(LauncherState *state, const char *raw_path) {
return FALSE;
}
- display_name = g_app_info_get_display_name(G_APP_INFO(app));
- if (display_name == NULL || *display_name == '\0') {
- display_name = g_app_info_get_name(G_APP_INFO(app));
+ safe_name = build_safe_display_name(app, path);
+ if (safe_name == NULL) {
+ g_clear_object(&app);
+ g_free(path);
+ return FALSE;
}
entry = g_new0(AppEntry, 1);
entry->desktop_path = path;
- entry->name = g_strdup(display_name != NULL ? display_name : raw_path);
+ entry->name = safe_name;
entry->app = app;
g_ptr_array_add(state->entries, entry);
+ g_hash_table_add(state->seen_paths, g_strdup(path));
return TRUE;
}
+// nav: input loading
static void load_paths_from_stream(LauncherState *state, FILE *stream, const char *source_name) {
- char *line = NULL;
- size_t line_cap = 0;
- ssize_t line_len = 0;
+ char line[MAX_INPUT_LINE_BYTES + 2];
guint line_no = 0;
- while ((line_len = getline(&line, &line_cap, stream)) != -1) {
+ while (fgets(line, sizeof(line), stream) != NULL) {
char *trimmed = NULL;
+ size_t line_len = strlen(line);
+ gboolean long_line = FALSE;
+
line_no++;
if (line_len > 0 && line[line_len - 1] == '\n') {
line[line_len - 1] = '\0';
+ } else if (!feof(stream)) {
+ int ch = 0;
+ long_line = TRUE;
+ while ((ch = fgetc(stream)) != '\n' && ch != EOF) {
+ }
+ }
+
+ if (long_line) {
+ g_printerr("skipping long line at %s:%u\n", source_name, line_no);
+ continue;
}
trimmed = trim_in_place(line);
@@ -132,9 +236,16 @@ static void load_paths_from_stream(LauncherState *state, FILE *stream, const cha
if (!add_desktop_path(state, trimmed)) {
g_printerr("skipping invalid desktop path at %s:%u -> %s\n", source_name, line_no, trimmed);
}
+
+ if (state->entries->len >= MAX_DESKTOP_ENTRIES) {
+ g_printerr("entry limit reached (%u)\n", MAX_DESKTOP_ENTRIES);
+ break;
+ }
}
- free(line);
+ if (ferror(stream)) {
+ g_printerr("read error from %s: %s\n", source_name, g_strerror(errno));
+ }
}
static gboolean load_paths_from_stdin(LauncherState *state) {
@@ -193,11 +304,14 @@ static gboolean load_paths_from_config(LauncherState *state) {
load_paths_from_stream(state, config, path);
- fclose(config);
+ if (fclose(config) != 0) {
+ g_printerr("warning: failed to close config file: %s\n", path);
+ }
g_free(path);
return state->entries->len > old_len;
}
+// nav: ui events
static gboolean on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
(void)widget;
(void)event;
@@ -345,6 +459,7 @@ static gboolean on_list_leave(GtkWidget *widget, GdkEventCrossing *event, gpoint
return FALSE;
}
+// nav: ui build
static GtkWidget *create_entry_row(AppEntry *entry) {
GtkWidget *row = gtk_list_box_row_new();
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 26);
@@ -428,6 +543,7 @@ static void apply_css(void) {
g_object_unref(provider);
}
+// nav: process setup
static void ignore_nonfatal_signals(void) {
struct sigaction sa;
@@ -452,6 +568,13 @@ int main(int argc, char **argv) {
memset(&state, 0, sizeof(state));
state.entries = g_ptr_array_new_with_free_func(app_entry_free);
+ state.seen_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ if (state.entries == NULL || state.seen_paths == NULL) {
+ g_printerr("failed to initialize launcher state\n");
+ launcher_state_cleanup(&state);
+ return EXIT_FAILURE;
+ }
ignore_nonfatal_signals();
@@ -462,13 +585,13 @@ int main(int argc, char **argv) {
if (!loaded_from_stdin && !loaded_from_config) {
g_printerr("no desktop entries found from stdin or config\n");
- g_ptr_array_free(state.entries, TRUE);
+ launcher_state_cleanup(&state);
return EXIT_FAILURE;
}
if (state.entries->len == 0) {
g_printerr("no valid desktop entries were loaded\n");
- g_ptr_array_free(state.entries, TRUE);
+ launcher_state_cleanup(&state);
return EXIT_FAILURE;
}
@@ -526,6 +649,6 @@ int main(int argc, char **argv) {
gtk_main();
- g_ptr_array_free(state.entries, TRUE);
+ launcher_state_cleanup(&state);
return EXIT_SUCCESS;
}