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 }