* 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. */
| 254 | * Returns 0 on success (or absent file), -1 on alloc failure. |
| 255 | */ |
| 256 | static int load_config_file(const char *path, cbm_userext_t **entries, int *count, |
| 257 | char source_sha256[CBM_SHA256_HEX_LEN + 1]) { |
| 258 | userconfig_source_digest("missing-or-unreadable", NULL, 0, source_sha256); |
| 259 | FILE *f = cbm_fopen(path, "rb"); |
| 260 | if (!f) { |
| 261 | return 0; /* file absent — silently ignore */ |
| 262 | } |
| 263 | |
| 264 | if (fseek(f, 0, SEEK_END) != 0) { |
| 265 | (void)fclose(f); |
| 266 | userconfig_source_digest("seek-error", NULL, 0, source_sha256); |
| 267 | return 0; |
| 268 | } |
| 269 | long len = ftell(f); |
| 270 | if (fseek(f, 0, SEEK_SET) != 0) { |
| 271 | (void)fclose(f); |
| 272 | userconfig_source_digest("seek-error", NULL, 0, source_sha256); |
| 273 | return 0; |
| 274 | } |
| 275 | |
| 276 | if (len <= 0 || len > MAX_CONFIG_SIZE) { |
| 277 | (void)fclose(f); |
| 278 | if (len > MAX_CONFIG_SIZE) { |
| 279 | cbm_log_warn("userconfig.file_too_large", "path", path); |
| 280 | userconfig_source_digest("oversized", NULL, 0, source_sha256); |
| 281 | } else { |
| 282 | userconfig_source_digest("empty", NULL, 0, source_sha256); |
| 283 | } |
| 284 | return 0; |
| 285 | } |
| 286 | |
| 287 | char *buf = malloc((size_t)len + SKIP_ONE); |
| 288 | if (!buf) { |
| 289 | (void)fclose(f); |
| 290 | return CBM_NOT_FOUND; |
| 291 | } |
| 292 | |
| 293 | size_t nread = fread(buf, SKIP_ONE, (size_t)len, f); |
| 294 | (void)fclose(f); |
| 295 | if (nread > (size_t)len) { |
| 296 | nread = (size_t)len; |
| 297 | } |
| 298 | buf[nread] = '\0'; |
| 299 | userconfig_source_digest("present", buf, nread, source_sha256); |
| 300 | |
| 301 | yyjson_doc *doc = yyjson_read(buf, nread, 0); |
| 302 | free(buf); |
| 303 | |
| 304 | if (!doc) { |
| 305 | cbm_log_warn("userconfig.corrupt_json", "path", path); |
| 306 | return 0; /* corrupt JSON — silently ignore (fail-open) */ |
| 307 | } |
| 308 | |
| 309 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 310 | int rc = parse_extra_extensions(root, entries, count, path); |
| 311 | yyjson_doc_free(doc); |
| 312 | return rc; |
| 313 | } |
no test coverage detected