cgi.c - frontends - front-ends for some sites (experiment) (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) LICENSE --- cgi.c (9039B) --- 1 #include <sys/socket.h> 2 #include <sys/types.h> 3 4 #include <ctype.h> 5 #include <errno.h> 6 #include <netdb.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <time.h> 12 #include <unistd.h> 13 14 #include "reddit.h" 15 16 #define OUT(s) (fputs((s), stdout)) 17 18 extern char **environ; 19 20 static struct list_response *response; 21 static time_t now; 22 23 /* CGI parameters */ 24 static char rawsearch[4096], search[4096]; 25 static char subreddit[1024], mode[16], slimit[32], order[16]; 26 static long limit = 100; 27 28 void 29 parsecgi(void) 30 { 31 char *query, *p; 32 size_t len; 33 34 if (!(query = getenv("QUERY_STRING"))) 35 query = ""; 36 37 /* subreddit: select subreddit */ 38 if ((p = getparam(query, "subreddit"))) { 39 if (decodeparam(subreddit, sizeof(subreddit), p) == -1) 40 subreddit[0] = '\0'; 41 } 42 43 /* order */ 44 if ((p = getparam(query, "o"))) { 45 if (decodeparam(order, sizeof(order), p) == -1 || 46 (strcmp(order, "hot") && 47 strcmp(order, "new") && 48 strcmp(order, "rising") && 49 strcmp(order, "controversial"))) 50 order[0] = '\0'; 51 } 52 if (!order[0]) 53 snprintf(order, sizeof(order), "hot"); 54 55 #if 0 56 /* TODO */ 57 /* page */ 58 if ((p = getparam(query, "page"))) { 59 if (decodeparam(page, sizeof(page), p) == -1) 60 page[0] = '\0'; 61 /* check if it's a number >= 0 and < 100 */ 62 errno = 0; 63 curpage = strtol(page, NULL, 10); 64 if (errno || curpage < 0 || curpage > 100) { 65 curpage = 1; 66 page[0] = '\0'; 67 } 68 } 69 #endif 70 71 /* limit */ 72 if ((p = getparam(query, "limit"))) { 73 if (decodeparam(slimit, sizeof(slimit), p) == -1) 74 slimit[0] = '\0'; 75 /* check if it's a number > 0 and < 100 */ 76 errno = 0; 77 limit = strtol(slimit, NULL, 10); 78 if (errno || limit <= 0 || limit > 100) { 79 limit = 100; /* default */ 80 slimit[0] = '\0'; 81 } 82 } 83 84 /* mode */ 85 if ((p = getparam(query, "m"))) { 86 if (decodeparam(mode, sizeof(mode), p) != -1) { 87 /* fixup first character (label) for matching */ 88 if (mode[0]) 89 mode[0] = tolower((unsigned char)mode[0]); 90 /* allowed themes */ 91 if (strcmp(mode, "light") && 92 strcmp(mode, "dark") && 93 strcmp(mode, "pink") && 94 strcmp(mode, "templeos")) 95 mode[0] = '\0'; 96 } 97 } 98 if (!mode[0]) 99 snprintf(mode, sizeof(mode), "light"); 100 101 /* search */ 102 if ((p = getparam(query, "q"))) { 103 if ((len = strcspn(p, "&")) && len + 1 < sizeof(rawsearch)) { 104 memcpy(rawsearch, p, len); 105 rawsearch[len] = '\0'; 106 } 107 108 if (decodeparam(search, sizeof(search), p) == -1) { 109 OUT("Status: 401 Bad Request\r\n\r\n"); 110 exit(1); 111 } 112 } 113 } 114 115 int 116 render(struct list_response *r) 117 { 118 struct item *items = r->items, *item; 119 char tmp[64], timebuf[32], baseurl[256]; 120 char *start, *end; 121 int i; 122 123 #if 0 124 if (pledge("stdio", NULL) == -1) { 125 OUT("Status: 500 Internal Server Error\r\n\r\n"); 126 exit(1); 127 } 128 #endif 129 130 OUT( 131 "Content-Type: text/html; charset=utf-8\r\n\r\n" 132 "<!DOCTYPE html>\n<html>\n<head>\n" 133 "<meta name=\"referrer\" content=\"no-referrer\" />\n" 134 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" 135 "<title>"); 136 if (r->nitems && subreddit[0]) { 137 OUT("r/"); 138 xmlencode(subreddit); 139 OUT(" - Reddit"); 140 } else { 141 OUT("Reddit"); 142 } 143 OUT("</title>\n"); 144 OUT( 145 "<link rel=\"stylesheet\" href=\"css/"); 146 xmlencode(mode); 147 OUT( 148 ".css\" type=\"text/css\" media=\"screen\" />\n" 149 "<link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />\n" 150 "<meta content=\"width=device-width\" name=\"viewport\" />\n" 151 "</head>\n" 152 "<body class=\"search\">\n" 153 "<form method=\"get\" action=\"\">\n"); 154 155 OUT("<input type=\"hidden\" name=\"m\" value=\""); 156 xmlencode(mode); 157 OUT("\" />\n"); 158 if (subreddit[0]) { 159 OUT("<input type=\"hidden\" name=\"subreddit\" value=\""); 160 xmlencode(subreddit); 161 OUT("\" />\n"); 162 } 163 OUT("<input type=\"hidden\" name=\"limit\" value=\""); 164 xmlencode("100"); /* TODO maybe */ 165 OUT("\" />\n"); 166 OUT("<input type=\"hidden\" name=\"order\" value=\""); 167 xmlencode("hot"); /* TODO maybe */ 168 OUT("\" />\n"); 169 170 OUT( 171 "<table class=\"search\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n" 172 "<tr>\n" 173 "<td width=\"100%\" class=\"input\">\n" 174 " <input type=\"search\" name=\"q\" value=\"\" placeholder=\"Search...\" size=\"72\" autofocus=\"autofocus\" class=\"search\" accesskey=\"f\" />\n" 175 "</td>\n" 176 "<td nowrap class=\"nowrap\">\n" 177 " <input type=\"submit\" value=\"Search\" class=\"button\"/>\n" 178 " <select name=\"o\" title=\"Order by\" accesskey=\"o\">\n" 179 " <option value=\"hot\" selected=\"selected\">Hot</option>\n" 180 " <option value=\"new\">New</option>\n" 181 " <option value=\"rising\">Rising</option>\n" 182 " <option value=\"controversial\">Controversial</option>\n" 183 " </select>\n" 184 " <label for=\"m\">Style: </label>\n"); 185 186 if (!strcmp(mode, "light")) 187 OUT("\t\t<input type=\"submit\" name=\"m\" value=\"Dark\" title=\"Dark mode\" id=\"m\" accesskey=\"s\"/>\n"); 188 else 189 OUT("\t\t<input type=\"submit\" name=\"m\" value=\"Light\" title=\"Light mode\" id=\"m\" accesskey=\"s\"/>\n"); 190 191 OUT( 192 " </td>\n" 193 "</tr>\n" 194 "</table>\n" 195 "</form>\n"); 196 197 if (r->nitems) { 198 OUT( 199 "<hr/>\n" 200 "<table class=\"items\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n" 201 "<tbody>\n"); 202 203 for (i = 0; i < r->nitems; i++) { 204 item = r->items + i; 205 206 OUT( 207 "<tr>\n" 208 "<td valign=\"middle\" align=\"center\" class=\"score\" width=\"100\">\n" 209 " \n"); 210 printf("%zu", item->ups); /* upvotes / score */ 211 OUT( 212 "</td>\n" 213 "<td valign=\"middle\" align=\"center\" class=\"thumb\" width=\"140\">\n"); 214 215 if (item->thumbnail[0] && !strncmp(item->thumbnail, "https://", 8)) { 216 OUT("<a href=\""); 217 218 /* link directly to dash url for video */ 219 if (item->is_video) 220 xmlencode(item->dash_url); 221 else 222 xmlencode(item->url); 223 224 OUT("\"><img src=\""); 225 xmlencode(item->thumbnail); 226 OUT("\" width=\"140\" alt=\"\" /></a>"); 227 } 228 229 OUT( 230 "</td>\n" 231 "<td valign=\"top\">\n" 232 " <h2><a href=\""); 233 /* link directly to dash url for video */ 234 if (item->is_video) 235 xmlencode(item->dash_url); 236 else 237 xmlencode(item->url); 238 OUT("\">"); 239 240 /* base url of url: somesite.org */ 241 baseurl[0] = '\0'; 242 if (!item->is_video && item->url[0]) { 243 if ((start = strstr(item->url, "://"))) { 244 start += strlen("://"); 245 if ((end = strstr(start, "/"))) { 246 if (end - start + 1 < sizeof(baseurl)) { 247 memcpy(baseurl, start, end - start); 248 baseurl[end - start] = '\0'; 249 } 250 } 251 } 252 } 253 254 if (item->is_video) 255 OUT("[video] "); 256 xmlencode(item->title); 257 258 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M", &(item->created_tm)); 259 260 OUT("</a></h2>\n"); 261 OUT( 262 " <time datetime=\""); 263 OUT(timebuf); 264 OUT("\">Submitted "); 265 if (!friendlytime(item->created_utc)) { 266 OUT("on "); 267 OUT(timebuf); 268 } 269 OUT(" by</time>\n" 270 " <a href=\"\">"); 271 xmlencode(item->author); 272 OUT("</a>\n"); 273 274 /* global view: show subreddit */ 275 if (!subreddit[0]) { 276 /* TODO: link */ 277 OUT(" in r/"); 278 xmlencode(item->subreddit); 279 } 280 281 OUT( 282 " <br/>\n" 283 " <br/>\n" 284 " <a href=\"https://old.reddit.com/"); 285 xmlencode(item->permalink); 286 OUT("\">"); 287 288 printf("%zu", item->num_comments); 289 290 OUT(" comments</a>\n" 291 "</td>\n" 292 "<td valign=\"top\" align=\"right\" class=\"tags\">\n" 293 " <span class=\"base\">"); 294 OUT(baseurl); 295 OUT( 296 "</span>\n" 297 " <br/>\n" 298 " <span class=\"tag\">"); 299 300 /* TODO: + flair color */ 301 xmlencode(item->link_flair_text); 302 OUT(" </span>\n" 303 "</td>\n" 304 "</tr>\n"); 305 306 OUT("<tr><td colspan=\"4\"><hr/></td></tr>\n"); 307 } 308 OUT("</tbody>\n"); 309 310 /* pagination does not work for user/channel search */ 311 if (r->before[0] || r->after[0]) { 312 OUT( 313 "<tfoot>\n" 314 "<tr>\n" 315 "\t<td align=\"left\" class=\"nowrap\" nowrap>\n"); 316 if (r->before[0]) { 317 OUT("\t\t<a href=\"?q="); 318 xmlencode(""); // TODO: remove q param later 319 OUT("&before="); 320 xmlencode(r->before); 321 OUT("&m="); 322 xmlencode(mode); 323 OUT("\" rel=\"prev\" accesskey=\"p\">← prev</a>\n"); 324 } 325 if (r->after[0]) { 326 OUT( 327 "\t</td>\n\t<td colspan=\"2\"></td>\n" 328 "\t<td align=\"right\" class=\"a-r nowrap\" nowrap>\n"); 329 OUT("\t\t<a href=\"?q="); // TODO: remove q param later. 330 xmlencode(""); // 331 OUT("&after="); 332 xmlencode(r->after); 333 OUT("&m="); 334 xmlencode(mode); 335 OUT("\" rel=\"next\" accesskey=\"n\">next →</a>\n"); 336 } 337 338 OUT( 339 "\t</td>\n" 340 "</tr>\n" 341 "</tfoot>\n"); 342 } 343 OUT("</table>\n"); 344 } 345 346 OUT("</body>\n</html>\n"); 347 348 return 0; 349 } 350 351 int 352 main(void) 353 { 354 struct list_response *r; 355 356 #if 0 357 if (pledge("stdio dns inet rpath unveil", NULL) == -1 || 358 unveil(TLS_CA_CERT_FILE, "r") == -1 || 359 unveil(NULL, NULL) == -1) { 360 OUT("Status: 500 Internal Server Error\r\n\r\n"); 361 exit(1); 362 } 363 #endif 364 365 parsecgi(); 366 367 #if 0 368 if (!rawsearch[0] && !chan[0] && !user[0]) 369 goto show; 370 #endif 371 372 r = reddit_list(subreddit, ""); // TODO: page 373 if (!r || r->nitems <= 0) { 374 OUT("Status: 500 Internal Server Error\r\n\r\n"); 375 exit(1); 376 } 377 378 show: 379 now = time(NULL); 380 render(r); 381 382 return 0; 383 }