brewery
notashelf /
74fed80a26f39bb04966d12c649a55150724965f

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git

Commit 74fed80a26f3

tarball

NotAShelf <raf@notashelf.dev> · 2025-11-01 22:19 UTC

unverified3 files changed+202-28
config: fix tilde expansion for wallpaper paths

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I45b7a520f1959886793ded62f1ed2cd96a6a6964
diff --git a/src/config.c b/src/config.cindex 5fcbc9e..8a9551a 100644--- a/src/config.c+++ b/src/config.c@@ -67,20 +67,32 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name,     return CHROMA_ERROR_MEMORY;   } -  chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];+  // Validate string lengths to prevent buffer overflow+  size_t output_len = strlen(output_name);+  size_t path_len = strlen(image_path); -  strncpy(mapping->output_name, output_name, sizeof(mapping->output_name) - 1);-  mapping->output_name[sizeof(mapping->output_name) - 1] = '\0';+  if (output_len >= sizeof(config->mappings[0].output_name)) {+    chroma_log("ERROR", "Output name too long: %s (max %zu)", output_name,+               sizeof(config->mappings[0].output_name) - 1);+    return CHROMA_ERROR_CONFIG;+  } -  strncpy(mapping->image_path, image_path, sizeof(mapping->image_path) - 1);-  mapping->image_path[sizeof(mapping->image_path) - 1] = '\0';+  if (path_len >= sizeof(config->mappings[0].image_path)) {+    chroma_log("ERROR", "Image path too long: %s (max %zu)", image_path,+               sizeof(config->mappings[0].image_path) - 1);+    return CHROMA_ERROR_CONFIG;+  }++  chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];++  strcpy(mapping->output_name, output_name);+  strcpy(mapping->image_path, image_path);    config->mapping_count++;    chroma_log("DEBUG", "Added mapping: %s -> %s", output_name, image_path);   chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",-             config->mapping_count, output_name, image_path,-             strlen(image_path));+             config->mapping_count, output_name, image_path, path_len);   return CHROMA_OK; } @@ -138,11 +150,29 @@ static int parse_config_line(chroma_config_t *config, char *line,    // Parse configuration options   if (strcasecmp(key, "default_image") == 0) {-    strncpy(config->default_image, value, sizeof(config->default_image) - 1);-    config->default_image[sizeof(config->default_image) - 1] = '\0';-    chroma_log("DEBUG", "Set default image: %s", value);-    chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",-               strlen(value), value);+    char *expanded_path = chroma_expand_path(value);+    const char *path_to_use = expanded_path ? expanded_path : value;+    size_t path_len = strlen(path_to_use);++    if (path_len >= sizeof(config->default_image)) {+      chroma_log("ERROR", "Default image path too long: %s (max %zu)",+                 path_to_use, sizeof(config->default_image) - 1);+      if (expanded_path) {+        free(expanded_path);+      }+      return CHROMA_ERROR_CONFIG;+    }++    strcpy(config->default_image, path_to_use);++    if (expanded_path) {+      chroma_log("DEBUG", "Set default image: %s -> %s", value, expanded_path);+      chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",+                 path_len, expanded_path);+      free(expanded_path);+    } else {+      chroma_log("WARN", "Failed to expand path, using original: %s", value);+    }   } else if (strcasecmp(key, "daemon") == 0 ||              strcasecmp(key, "daemon_mode") == 0) {     config->daemon_mode = parse_bool(value);@@ -160,18 +190,33 @@ static int parse_config_line(chroma_config_t *config, char *line,       return CHROMA_OK;     } +    // Expand path before validation and storage+    char *expanded_path = chroma_expand_path(value);+    const char *path_to_validate = expanded_path ? expanded_path : value;+     // Validate image path-    if (chroma_image_validate(value) != CHROMA_OK) {-      chroma_log("WARN", "Invalid image path for output %s: %s", output_name,-                 value);+    if (chroma_image_validate(path_to_validate) != CHROMA_OK) {+      chroma_log("WARN", "Invalid image path for output %s: %s (expanded: %s)",+                 output_name, value, path_to_validate);+      if (expanded_path) {+        free(expanded_path);+      }       return CHROMA_OK;     } -    if (add_output_mapping(config, output_name, value) != CHROMA_OK) {+    if (add_output_mapping(config, output_name, path_to_validate) !=+        CHROMA_OK) {       chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,-                 value);+                 path_to_validate);+      if (expanded_path) {+        free(expanded_path);+      }       return CHROMA_ERROR_CONFIG;     }++    if (expanded_path) {+      free(expanded_path);+    }   } else {     chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,                key);diff --git a/src/image.c b/src/image.cindex 53e03d8..6a22991 100644--- a/src/image.c+++ b/src/image.c@@ -4,6 +4,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h>+#include <strings.h> #include <sys/stat.h>  #include "../include/chroma.h"diff --git a/src/utils.c b/src/utils.cindex 8b66fd3..5341c5b 100644--- a/src/utils.c+++ b/src/utils.c@@ -94,18 +94,138 @@ void chroma_cleanup_signals(void) {   g_config_file = NULL; } -// Expand tilde in path+// Expand environment variables in a string+static char *expand_env_vars(const char *str) {+  if (!str || strchr(str, '$') == NULL) {+    return strdup(str);+  }++  char *result = strdup("");+  if (!result) {+    return NULL;+  }++  const char *p = str;+  while (*p) {+    if (*p == '$') {+      p++;+      if (*p == '{') {+        // ${VAR} format+        p++;+        const char *end = strchr(p, '}');+        if (!end) {+          // No closing brace, treat as literal+          char *tmp = realloc(result, strlen(result) + 2);+          if (!tmp) {+            free(result);+            return NULL;+          }+          result = tmp;+          strcat(result, "${");+          break;+        }++        size_t var_len = end - p;+        char *var_name = malloc(var_len + 1);+        if (!var_name) {+          free(result);+          return NULL;+        }+        strncpy(var_name, p, var_len);+        var_name[var_len] = '\0';++        const char *var_value = getenv(var_name);+        if (var_value) {+          char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);+          if (!tmp) {+            free(var_name);+            free(result);+            return NULL;+          }+          result = tmp;+          strcat(result, var_value);+        }++        free(var_name);+        p = end + 1;+      } else {+        // $VAR format+        const char *start = p;+        while (*p && (isalnum((unsigned char)*p) || *p == '_')) {+          p++;+        }++        if (p == start) {+          // Not a valid variable name, treat $ as literal+          char *tmp = realloc(result, strlen(result) + 2);+          if (!tmp) {+            free(result);+            return NULL;+          }+          result = tmp;+          strcat(result, "$");+        } else {+          size_t var_len = p - start;+          char *var_name = malloc(var_len + 1);+          if (!var_name) {+            free(result);+            return NULL;+          }+          strncpy(var_name, start, var_len);+          var_name[var_len] = '\0';++          const char *var_value = getenv(var_name);+          if (var_value) {+            char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);+            if (!tmp) {+              free(var_name);+              free(result);+              return NULL;+            }+            result = tmp;+            strcat(result, var_value);+          }++          free(var_name);+        }+      }+    } else {+      // Regular character+      size_t len = strlen(result);+      char *tmp = realloc(result, len + 2);+      if (!tmp) {+        free(result);+        return NULL;+      }+      result = tmp;+      result[len] = *p;+      result[len + 1] = '\0';+      p++;+    }+  }++  return result;+}++// Expand tilde and environment variables in path char *chroma_expand_path(const char *path) {   if (!path) {     return NULL;   } -  if (path[0] != '~') {-    return strdup(path);+  // First expand environment variables+  char *env_expanded = expand_env_vars(path);+  if (!env_expanded) {+    return NULL;+  }++  // Then expand tilde if present+  if (env_expanded[0] != '~') {+    return env_expanded;   }    const char *home;-  if (path[1] == '/' || path[1] == '\0') {+  if (env_expanded[1] == '/' || env_expanded[1] == '\0') {     // ~/... or just ~     home = getenv("HOME");     if (!home) {@@ -116,34 +236,39 @@ char *chroma_expand_path(const char *path) {     }     if (!home) {       chroma_log("ERROR", "Could not determine home directory");+      free(env_expanded);       return strdup(path); // Return original path as fallback     }      size_t home_len = strlen(home);-    size_t path_len = strlen(path);+    size_t path_len = strlen(env_expanded);     char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0     if (!expanded) {       chroma_log("ERROR", "Failed to allocate memory for path expansion");+      free(env_expanded);       return strdup(path);     }      strcpy(expanded, home);-    if (path[1] == '/') {-      strcat(expanded, path + 1);+    if (env_expanded[1] == '/') {+      strcat(expanded, env_expanded + 1);     } +    free(env_expanded);     return expanded;   } else {     // ~user/...-    const char *slash = strchr(path, '/');-    size_t user_len = slash ? (size_t)(slash - path - 1) : strlen(path) - 1;+    const char *slash = strchr(env_expanded, '/');+    size_t user_len =+        slash ? (size_t)(slash - env_expanded - 1) : strlen(env_expanded) - 1;      char *username = malloc(user_len + 1);     if (!username) {+      free(env_expanded);       return strdup(path);     } -    strncpy(username, path + 1, user_len);+    strncpy(username, env_expanded + 1, user_len);     username[user_len] = '\0';      struct passwd *pw = getpwnam(username);@@ -151,6 +276,7 @@ char *chroma_expand_path(const char *path) {     if (!pw) {       chroma_log("ERROR", "User not found: %s", username);       free(username);+      free(env_expanded);       return strdup(path);     } @@ -160,6 +286,7 @@ char *chroma_expand_path(const char *path) {     size_t remaining_len = slash ? strlen(slash) : 0;     char *expanded = malloc(home_len + remaining_len + 1);     if (!expanded) {+      free(env_expanded);       return strdup(path);     } @@ -168,6 +295,7 @@ char *chroma_expand_path(const char *path) {       strcat(expanded, slash);     } +    free(env_expanded);     return expanded;   } }@@ -413,4 +541,4 @@ void chroma_format_memory_size(size_t bytes, char *buffer, size_t buffer_size) { }  // Cleanup utility functions-void chroma_utils_cleanup(void) { chroma_cleanup_signals(); }\ No newline at end of file+void chroma_utils_cleanup(void) { chroma_cleanup_signals(); }