readr

Minimal Terminal RSS Reader
Log | Files | Refs | README | LICENSE

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;
}