diff options
| author | kj_sh604 | 2026-05-26 20:38:05 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-05-26 20:38:05 -0400 |
| commit | 64b2194b38f5bb7891c5a58f425d5814536d3343 (patch) | |
| tree | 701595865166c42a3534d7e6c8c61b199484e310 | |
| parent | a71dbc43e71da93784d09db5fa37ae7c103a6ead (diff) | |
refactor: memory management and security changes
also add navigation code comments
| -rw-r--r-- | src/dapp.c | 155 |
1 files changed, 139 insertions, 16 deletions
@@ -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; } |
