readr

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

utils.c (6507B)


      1 #include <stdio.h>
      2 #include <strings.h>
      3 #include <errno.h>
      4 #include <stdarg.h>
      5 #include <locale.h>
      6 
      7 #include "utils.h"
      8 #include "config.h"
      9 
     10 void
     11 validate_config(void)
     12 {
     13 	if (strlen(FEEDS) < 3 || strlen(FEEDS) > 1024) panic("invalid FEEDS path");
     14 	if (strlen(DB_PATH) < 3 || strlen(DB_PATH) > 1024) panic("invalid DB_PATH path");
     15 
     16 	if (MAX_POST_PER_FEED < 1 || MAX_POST_PER_FEED > 256) panic("invalid MAX_POST_PER_FEED (1~256)");
     17 	if (POSTS_CAP < 16 || POSTS_CAP > 512) panic("invalid POSTS_CAP (16~512)");
     18 	if (FEEDS_CAP < 16 || FEEDS_CAP > 512) panic("invalid FEEDS_CAP (16~512)");
     19 	if (TITLE_CAP < 16 || TITLE_CAP > 512) panic("invalid TITLE_CAP (16~512)");
     20 	if (DOMAIN_CAP < 16 || DOMAIN_CAP > 512) panic("invalid DOMAIN_CAP (16~512)");
     21 	if (URL_CAP < 2048 || URL_CAP > 8192) panic("invalid URL_CAP (2048~8192)");
     22 }
     23 
     24 void
     25 switch_locale(void)
     26 {
     27 	// for unicode blocks/lines, curvy quotes etc.
     28 	if (!setlocale(LC_ALL, "C.UTF-8")) panic("could not switch to UTF-8!");
     29 }
     30 
     31 void
     32 panic(const char* fmt, ...)
     33 {
     34 	va_list args;
     35 	int saved_errno;
     36 	saved_errno = errno;
     37 	va_start(args, fmt);
     38 	fprintf(stderr, "Error: ");
     39 	vfprintf(stderr, fmt, args);
     40 	fprintf(stderr, "\n");
     41 	va_end(args);
     42 	if (fmt[0] && fmt[strlen(fmt) - 1] == ':') fprintf(stderr, " %s", strerror(saved_errno));
     43 	fputc('\n', stderr);
     44 	exit(1);
     45 }
     46 
     47 #if defined(_WIN32)
     48 
     49 // to be tested on windows, and then have a shower
     50 
     51 const char*
     52 get_home_dir(void)
     53 {
     54 	const char* p = getenv("USERPROFILE");
     55 	if (p && *p) return p;
     56 	const char* drv = getenv("HOMEDRIVE");
     57 	const char* pth = getenv("HOMEPATH");
     58 	if (drv && pth) {
     59 		size_t n = strlen(drv) + strlen(pth) + 1;
     60 		char* buf = (char*)ecalloc(n, sizeof(char));
     61 		snprintf(buf, n, "%s%s", drv, pth);
     62 		return buf; /* caller frees if not an env ptr */
     63 	}
     64 	return NULL;
     65 }
     66 #else
     67 
     68 // macos/linux
     69 
     70 #include <pwd.h>
     71 #include <unistd.h>
     72 
     73 const char*
     74 get_home_dir(void)
     75 {
     76 	const char* p = getenv("HOME");
     77 	if (p && *p) return p;
     78 	return NULL;
     79 }
     80 
     81 #endif
     82 
     83 const char*
     84 expand_tilde(const char* path)
     85 {
     86 	if (!path) return NULL;
     87 	const char* home = get_home_dir();
     88 	if (!home) home = ""; /* degrade to empty if unknown */
     89 
     90 	/* Worst case: every char is '~' → expand to home each time */
     91 	size_t hlen = strlen(home);
     92 	size_t out_cap = 1; /* for '\0' */
     93 	for (const char* s = path; *s; ++s)
     94 		out_cap += (*s == '~') ? hlen : 1;
     95 
     96 	char* out = (char*)ecalloc(out_cap, sizeof(char));
     97 
     98 	char* w = out;
     99 	for (const char* s = path; *s; ++s) {
    100 		if (*s == '~') {
    101 			memcpy(w, home, hlen);
    102 			w += hlen;
    103 		} else {
    104 			*w++ = *s;
    105 		}
    106 	}
    107 	*w = '\0';
    108 
    109 #if defined(_WIN32)
    110 	/* If get_home_dir() allocated (HOMEDRIVE+HOMEPATH path), free it. */
    111 	const char* up = getenv("USERPROFILE");
    112 	if (!(up && *up)) { free((void*)home); }
    113 #endif
    114 	return out;
    115 }
    116 
    117 char*
    118 read_file(const char* file_path)
    119 {
    120 	FILE* fp = fopen(file_path, "rb");
    121 	if (fp == NULL) { return NULL; }
    122 
    123 	if (fseek(fp, 0, SEEK_END) != 0) {
    124 		fclose(fp);
    125 		printf("Failed to find the end of the file\n");
    126 		return NULL;
    127 	}
    128 
    129 	long file_size = ftell(fp);
    130 
    131 	if (file_size < 0) {
    132 		fclose(fp);
    133 		printf("Failed to determine the file size\n");
    134 		return NULL;
    135 	}
    136 
    137 	rewind(fp);
    138 
    139 	// check for overflow before casting
    140 	if ((unsigned long)file_size >= SIZE_MAX) {
    141 		fclose(fp);
    142 		printf("File too large to fit in memory\n");
    143 		return NULL;
    144 	}
    145 
    146 	char* contents = (char*)ecalloc(1, (size_t)file_size + 1);
    147 
    148 	size_t bytes_read = fread(contents, 1, (size_t)file_size, fp);
    149 	if (bytes_read != (size_t)file_size) {
    150 		free(contents);
    151 		fclose(fp);
    152 		printf("Failed to read the file in its entirety\n");
    153 		return NULL;
    154 	}
    155 
    156 	contents[file_size] = '\0';
    157 
    158 	fclose(fp);
    159 	return contents;
    160 }
    161 
    162 char*
    163 host_from_url(const char* url)
    164 {
    165 	const char *p = url, *start, *end;
    166 
    167 	// skip scheme
    168 	const char* scheme = strstr(p, "://");
    169 	if (scheme)
    170 		p = scheme + 3;
    171 	else if (p[0] == '/' && p[1] == '/')
    172 		p += 2;
    173 
    174 	// IPv6 literal in brackets
    175 	if (*p == '[') {
    176 		start = p + 1;
    177 		end = strchr(start, ']');
    178 		if (!end) return NULL;
    179 		return strndup(start, (size_t)(end - start));
    180 	}
    181 
    182 	start = p;
    183 
    184 	// stop at / ? # : (drop path, query, fragment, and port)
    185 	end = strpbrk(start, "/?:#:");
    186 	if (!end) end = start + strlen(start);
    187 
    188 	// optional: strip leading "www."
    189 	if (end - start >= 4 && strncasecmp(start, "www.", 4) == 0) start += 4;
    190 
    191 	return strndup(start, (size_t)(end - start));
    192 }
    193 
    194 void
    195 remove_all_chars(char* str, char c)
    196 {
    197 	char *pr = str, *pw = str;
    198 	while (*pr) {
    199 		*pw = *pr++;
    200 		pw += (*pw != c);
    201 	}
    202 	*pw = '\0';
    203 }
    204 
    205 void
    206 remove_all_tags(char* str)
    207 {
    208 	char *pr = str, *pw = str;
    209 	int in_tag = 0;
    210 
    211 	while (*pr) {
    212 		if (*pr == '<') {
    213 			in_tag = 1;
    214 			pr++;
    215 			continue;
    216 		} else if (*pr == '>') {
    217 			in_tag = 0;
    218 			pr++;
    219 			continue;
    220 		}
    221 		if (!in_tag) { *pw++ = *pr; }
    222 		pr++;
    223 	}
    224 	*pw = '\0';
    225 }
    226 
    227 //
    228 // open_url
    229 //
    230 
    231 #define _POSIX_C_SOURCE 200809L
    232 
    233 #ifdef _WIN32
    234 #include <windows.h>
    235 int
    236 open_url(const char* url)
    237 {
    238 	HINSTANCE r = ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
    239 	return ((uintptr_t)r > 32) ? 0 : -1; // ShellExecute error if <= 32
    240 }
    241 #else
    242 static void
    243 shell_quote(const char* in, char* out, size_t outsz)
    244 {
    245 	// wrap in single quotes and escape embedded single quotes: ' -> '\''
    246 	size_t n = 0;
    247 	if (n + 1 < outsz) out[n++] = '\'';
    248 	for (; *in && n + 4 < outsz; ++in) {
    249 		if (*in == '\'') {
    250 			memcpy(out + n, "'\\''", 4);
    251 			n += 4;
    252 		} else
    253 			out[n++] = *in;
    254 	}
    255 	if (n + 2 <= outsz) {
    256 		out[n++] = '\'';
    257 		out[n] = '\0';
    258 	} else if (outsz)
    259 		out[outsz - 1] = '\0';
    260 }
    261 
    262 int
    263 open_url(const char* url)
    264 {
    265 	char q[4096];
    266 	char cmd[4600];
    267 	shell_quote(url, q, sizeof q);
    268 #ifdef __APPLE__
    269 	snprintf(cmd, sizeof cmd, "open %s >/dev/null 2>&1 &", q);
    270 #else
    271 	snprintf(cmd, sizeof cmd, "xdg-open %s >/dev/null 2>&1 &", q);
    272 #endif
    273 	return system(cmd); // 0 on success from the shell
    274 }
    275 #endif
    276 
    277 //
    278 // nonascii_replace
    279 //
    280 
    281 void
    282 nonascii_replace(char* s, char to)
    283 {
    284 	unsigned char *r = (unsigned char*)s, *w = (unsigned char*)s;
    285 	while (*r) {
    286 		if (*r < 0x80) { // ASCII byte
    287 			*w++ = *r++;
    288 		} else { // non-ASCII: skip UTF-8 seq, write one space
    289 			size_t need = 1;
    290 			if ((*r & 0xE0) == 0xC0)
    291 				need = 2;
    292 			else if ((*r & 0xF0) == 0xE0)
    293 				need = 3;
    294 			else if ((*r & 0xF8) == 0xF0)
    295 				need = 4;
    296 
    297 			size_t i = 1;
    298 			if (need > 1) {
    299 				for (; i < need && r[i]; ++i)
    300 					if ((r[i] & 0xC0) != 0x80) break;
    301 			}
    302 			r += (i == need) ? need : 1; // tolerate invalid UTF-8
    303 
    304 			if (w == (unsigned char*)s || w[-1] != ' ') *w++ = (unsigned char)to;
    305 		}
    306 	}
    307 	*w = '\0';
    308 }
    309 
    310 void*
    311 ecalloc(size_t c, size_t s)
    312 {
    313 	void* p;
    314 	if (!(p = calloc(c, s))) { panic("calloc:"); }
    315 	return p;
    316 }