utils.c (6507B)
#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <stdarg.h>
#include <locale.h>
#include "utils.h"
#include "config.h"
void
validate_config(void)
{
if (strlen(FEEDS) < 3 || strlen(FEEDS) > 1024) panic("invalid FEEDS path");
if (strlen(DB_PATH) < 3 || strlen(DB_PATH) > 1024) panic("invalid DB_PATH path");
if (MAX_POST_PER_FEED < 1 || MAX_POST_PER_FEED > 256) panic("invalid MAX_POST_PER_FEED (1~256)");
if (POSTS_CAP < 16 || POSTS_CAP > 512) panic("invalid POSTS_CAP (16~512)");
if (FEEDS_CAP < 16 || FEEDS_CAP > 512) panic("invalid FEEDS_CAP (16~512)");
if (TITLE_CAP < 16 || TITLE_CAP > 512) panic("invalid TITLE_CAP (16~512)");
if (DOMAIN_CAP < 16 || DOMAIN_CAP > 512) panic("invalid DOMAIN_CAP (16~512)");
if (URL_CAP < 2048 || URL_CAP > 8192) panic("invalid URL_CAP (2048~8192)");
}
void
switch_locale(void)
{
// for unicode blocks/lines, curvy quotes etc.
if (!setlocale(LC_ALL, "C.UTF-8")) panic("could not switch to UTF-8!");
}
void
panic(const char* fmt, ...)
{
va_list args;
int saved_errno;
saved_errno = errno;
va_start(args, fmt);
fprintf(stderr, "Error: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') fprintf(stderr, " %s", strerror(saved_errno));
fputc('\n', stderr);
exit(1);
}
#if defined(_WIN32)
// to be tested on windows, and then have a shower
const char*
get_home_dir(void)
{
const char* p = getenv("USERPROFILE");
if (p && *p) return p;
const char* drv = getenv("HOMEDRIVE");
const char* pth = getenv("HOMEPATH");
if (drv && pth) {
size_t n = strlen(drv) + strlen(pth) + 1;
char* buf = (char*)ecalloc(n, sizeof(char));
snprintf(buf, n, "%s%s", drv, pth);
return buf; /* caller frees if not an env ptr */
}
return NULL;
}
#else
// macos/linux
#include <pwd.h>
#include <unistd.h>
const char*
get_home_dir(void)
{
const char* p = getenv("HOME");
if (p && *p) return p;
return NULL;
}
#endif
const char*
expand_tilde(const char* path)
{
if (!path) return NULL;
const char* home = get_home_dir();
if (!home) home = ""; /* degrade to empty if unknown */
/* Worst case: every char is '~' → expand to home each time */
size_t hlen = strlen(home);
size_t out_cap = 1; /* for '\0' */
for (const char* s = path; *s; ++s)
out_cap += (*s == '~') ? hlen : 1;
char* out = (char*)ecalloc(out_cap, sizeof(char));
char* w = out;
for (const char* s = path; *s; ++s) {
if (*s == '~') {
memcpy(w, home, hlen);
w += hlen;
} else {
*w++ = *s;
}
}
*w = '\0';
#if defined(_WIN32)
/* If get_home_dir() allocated (HOMEDRIVE+HOMEPATH path), free it. */
const char* up = getenv("USERPROFILE");
if (!(up && *up)) { free((void*)home); }
#endif
return out;
}
char*
read_file(const char* file_path)
{
FILE* fp = fopen(file_path, "rb");
if (fp == NULL) { return NULL; }
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
printf("Failed to find the end of the file\n");
return NULL;
}
long file_size = ftell(fp);
if (file_size < 0) {
fclose(fp);
printf("Failed to determine the file size\n");
return NULL;
}
rewind(fp);
// check for overflow before casting
if ((unsigned long)file_size >= SIZE_MAX) {
fclose(fp);
printf("File too large to fit in memory\n");
return NULL;
}
char* contents = (char*)ecalloc(1, (size_t)file_size + 1);
size_t bytes_read = fread(contents, 1, (size_t)file_size, fp);
if (bytes_read != (size_t)file_size) {
free(contents);
fclose(fp);
printf("Failed to read the file in its entirety\n");
return NULL;
}
contents[file_size] = '\0';
fclose(fp);
return contents;
}
char*
host_from_url(const char* url)
{
const char *p = url, *start, *end;
// skip scheme
const char* scheme = strstr(p, "://");
if (scheme)
p = scheme + 3;
else if (p[0] == '/' && p[1] == '/')
p += 2;
// IPv6 literal in brackets
if (*p == '[') {
start = p + 1;
end = strchr(start, ']');
if (!end) return NULL;
return strndup(start, (size_t)(end - start));
}
start = p;
// stop at / ? # : (drop path, query, fragment, and port)
end = strpbrk(start, "/?:#:");
if (!end) end = start + strlen(start);
// optional: strip leading "www."
if (end - start >= 4 && strncasecmp(start, "www.", 4) == 0) start += 4;
return strndup(start, (size_t)(end - start));
}
void
remove_all_chars(char* str, char c)
{
char *pr = str, *pw = str;
while (*pr) {
*pw = *pr++;
pw += (*pw != c);
}
*pw = '\0';
}
void
remove_all_tags(char* str)
{
char *pr = str, *pw = str;
int in_tag = 0;
while (*pr) {
if (*pr == '<') {
in_tag = 1;
pr++;
continue;
} else if (*pr == '>') {
in_tag = 0;
pr++;
continue;
}
if (!in_tag) { *pw++ = *pr; }
pr++;
}
*pw = '\0';
}
//
// open_url
//
#define _POSIX_C_SOURCE 200809L
#ifdef _WIN32
#include <windows.h>
int
open_url(const char* url)
{
HINSTANCE r = ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
return ((uintptr_t)r > 32) ? 0 : -1; // ShellExecute error if <= 32
}
#else
static void
shell_quote(const char* in, char* out, size_t outsz)
{
// wrap in single quotes and escape embedded single quotes: ' -> '\''
size_t n = 0;
if (n + 1 < outsz) out[n++] = '\'';
for (; *in && n + 4 < outsz; ++in) {
if (*in == '\'') {
memcpy(out + n, "'\\''", 4);
n += 4;
} else
out[n++] = *in;
}
if (n + 2 <= outsz) {
out[n++] = '\'';
out[n] = '\0';
} else if (outsz)
out[outsz - 1] = '\0';
}
int
open_url(const char* url)
{
char q[4096];
char cmd[4600];
shell_quote(url, q, sizeof q);
#ifdef __APPLE__
snprintf(cmd, sizeof cmd, "open %s >/dev/null 2>&1 &", q);
#else
snprintf(cmd, sizeof cmd, "xdg-open %s >/dev/null 2>&1 &", q);
#endif
return system(cmd); // 0 on success from the shell
}
#endif
//
// nonascii_replace
//
void
nonascii_replace(char* s, char to)
{
unsigned char *r = (unsigned char*)s, *w = (unsigned char*)s;
while (*r) {
if (*r < 0x80) { // ASCII byte
*w++ = *r++;
} else { // non-ASCII: skip UTF-8 seq, write one space
size_t need = 1;
if ((*r & 0xE0) == 0xC0)
need = 2;
else if ((*r & 0xF0) == 0xE0)
need = 3;
else if ((*r & 0xF8) == 0xF0)
need = 4;
size_t i = 1;
if (need > 1) {
for (; i < need && r[i]; ++i)
if ((r[i] & 0xC0) != 0x80) break;
}
r += (i == need) ? need : 1; // tolerate invalid UTF-8
if (w == (unsigned char*)s || w[-1] != ' ') *w++ = (unsigned char)to;
}
}
*w = '\0';
}
void*
ecalloc(size_t c, size_t s)
{
void* p;
if (!(p = calloc(c, s))) { panic("calloc:"); }
return p;
}