* Read a JSON file and parse extra_extensions from it. * Silently ignores missing files. Logs warnings for corrupt JSON. * Returns 0 on success (or absent file), -1 on alloc failure. */
| 232 | * Returns 0 on success (or absent file), -1 on alloc failure. |
| 233 | */ |
| 234 | static int load_config_file(const char *path, cbm_userext_t **entries, int *count) { |
| 235 | FILE *f = cbm_fopen(path, "rb"); |
| 236 | if (!f) { |
| 237 | return 0; /* file absent — silently ignore */ |
| 238 | } |
| 239 | |
| 240 | if (fseek(f, 0, SEEK_END) != 0) { |
| 241 | (void)fclose(f); |
| 242 | return 0; |
| 243 | } |
| 244 | long len = ftell(f); |
| 245 | if (fseek(f, 0, SEEK_SET) != 0) { |
| 246 | (void)fclose(f); |
| 247 | return 0; |
| 248 | } |
| 249 | |
| 250 | if (len <= 0 || len > MAX_CONFIG_SIZE) { |
| 251 | (void)fclose(f); |
| 252 | if (len > MAX_CONFIG_SIZE) { |
| 253 | cbm_log_warn("userconfig.file_too_large", "path", path); |
| 254 | } |
| 255 | return 0; |
| 256 | } |
| 257 | |
| 258 | char *buf = malloc((size_t)len + SKIP_ONE); |
| 259 | if (!buf) { |
| 260 | (void)fclose(f); |
| 261 | return CBM_NOT_FOUND; |
| 262 | } |
| 263 | |
| 264 | size_t nread = fread(buf, SKIP_ONE, (size_t)len, f); |
| 265 | (void)fclose(f); |
| 266 | if (nread > (size_t)len) { |
| 267 | nread = (size_t)len; |
| 268 | } |
| 269 | buf[nread] = '\0'; |
| 270 | |
| 271 | yyjson_doc *doc = yyjson_read(buf, nread, 0); |
| 272 | free(buf); |
| 273 | |
| 274 | if (!doc) { |
| 275 | cbm_log_warn("userconfig.corrupt_json", "path", path); |
| 276 | return 0; /* corrupt JSON — silently ignore (fail-open) */ |
| 277 | } |
| 278 | |
| 279 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 280 | int rc = parse_extra_extensions(root, entries, count, path); |
| 281 | yyjson_doc_free(doc); |
| 282 | return rc; |
| 283 | } |
| 284 | |
| 285 | /* ── Public API ──────────────────────────────────────────────────── */ |
| 286 |
no test coverage detected