readr

Minimal RSS reader (WIP)
Log | Files | Refs | README | LICENSE

utils.c (5722B)


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