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("&amp;before=");
          320                                 xmlencode(r->before);
          321                                 OUT("&amp;m=");
          322                                 xmlencode(mode);
          323                                 OUT("\" rel=\"prev\" accesskey=\"p\">&larr; 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("&amp;after=");
          332                                 xmlencode(r->after);
          333                                 OUT("&amp;m=");
          334                                 xmlencode(mode);
          335                                 OUT("\" rel=\"next\" accesskey=\"n\">next &rarr;</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 }