cgi.c - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       cgi.c (10345B)
       ---
            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 <unistd.h>
           12 
           13 #include "https.h"
           14 #include "util.h"
           15 #include "youtube.h"
           16 
           17 #define OUT(s) (fputs((s), stdout))
           18 
           19 extern char **environ;
           20 
           21 /* page title */
           22 char title[1024];
           23 
           24 /* CGI parameters */
           25 static char rawsearch[4096], search[4096], order[16], page[64];
           26 static char videoid[256];
           27 static char channelid[256], userid[256];
           28 
           29 /* Escape characters below as HTML 2.0 / XML 1.0.
           30    Translate multi-line to <br/> */
           31 void
           32 xmlencode_multiline(const char *s)
           33 {
           34         for (; *s; s++) {
           35                 switch(*s) {
           36                 case '<':  fputs("&lt;", stdout);   break;
           37                 case '>':  fputs("&gt;", stdout);   break;
           38                 case '\'': fputs("&#39;", stdout);  break;
           39                 case '&':  fputs("&amp;", stdout);  break;
           40                 case '"':  fputs("&quot;", stdout); break;
           41                 case '\n': fputs("<br/>", stdout);  break;
           42                 default:   putchar(*s);
           43                 }
           44         }
           45 }
           46 
           47 void
           48 parsecgi(void)
           49 {
           50         char *query, *p;
           51         size_t len;
           52 
           53         if (!(query = getenv("QUERY_STRING")))
           54                 query = "";
           55 
           56         /* order */
           57         if ((p = getparam(query, "o"))) {
           58                 if (decodeparam(order, sizeof(order), p) == -1 ||
           59                         (strcmp(order, "date") &&
           60                         strcmp(order, "relevance") &&
           61                         strcmp(order, "views") &&
           62                         strcmp(order, "rating")))
           63                         order[0] = '\0';
           64         }
           65         if (!order[0])
           66                 snprintf(order, sizeof(order), "relevance");
           67 
           68         /* search */
           69         if ((p = getparam(query, "q"))) {
           70                 if ((len = strcspn(p, "&")) && len + 1 < sizeof(rawsearch)) {
           71                         memcpy(rawsearch, p, len);
           72                         rawsearch[len] = '\0';
           73                 }
           74 
           75                 if (decodeparam(search, sizeof(search), p) == -1) {
           76                         OUT("Status: 401 Bad Request\r\n\r\n");
           77                         exit(1);
           78                 }
           79         }
           80 
           81         /* channel ID */
           82         if ((p = getparam(query, "chan"))) {
           83                 if (decodeparam(channelid, sizeof(channelid), p) == -1)
           84                         channelid[0] = '\0';
           85         }
           86 
           87         /* user ID */
           88         if ((p = getparam(query, "user"))) {
           89                 if (decodeparam(userid, sizeof(userid), p) == -1)
           90                         userid[0] = '\0';
           91         }
           92 
           93         /* video ID */
           94         if ((p = getparam(query, "v"))) {
           95                 if (decodeparam(videoid, sizeof(videoid), p) == -1)
           96                         videoid[0] = '\0';
           97         }
           98 }
           99 
          100 void
          101 header(void)
          102 {
          103         OUT(
          104                 "Content-Type: text/html; charset=utf-8\r\n\r\n"
          105                 "<!DOCTYPE html>\n<html>\n<head>\n"
          106                 "<meta name=\"referrer\" content=\"no-referrer\" />\n"
          107                 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n");
          108 
          109         if (title[0]) {
          110                 OUT("<title>");
          111                 xmlencode(title);
          112                 OUT("</title>");
          113         }
          114 
          115         OUT(
          116                 "<link rel=\"stylesheet\" href=\"css/style.css\" type=\"text/css\" media=\"screen\" />\n"
          117                 "<link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />\n"
          118                 "<meta content=\"width=device-width\" name=\"viewport\" />\n"
          119                 "</head>\n"
          120                 "<body class=\"search\">\n"
          121                 "<form method=\"get\" action=\"\">\n");
          122 
          123         OUT(
          124                 "<table class=\"search\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n"
          125                 "<tr>\n"
          126                 "        <td width=\"100%\" class=\"input\">\n"
          127                 "                <input type=\"search\" name=\"q\" value=\"");
          128         xmlencode(search);
          129         OUT(
          130                 "\" placeholder=\"Search...\" size=\"72\" autofocus=\"autofocus\" class=\"search\" accesskey=\"f\" />\n"
          131                 "        </td>\n"
          132                 "        <td nowrap class=\"nowrap\">\n"
          133                 "                <input type=\"submit\" value=\"Search\" class=\"button\"/>\n"
          134                 "                <select name=\"o\" title=\"Order by\" accesskey=\"o\">\n");
          135         printf("                        <option value=\"date\"%s>Creation date</option>\n", !strcmp(order, "date") ? " selected=\"selected\"" : "");
          136         printf("                        <option value=\"relevance\"%s>Relevance</option>\n", !strcmp(order, "relevance") ? " selected=\"selected\"" : "");
          137         printf("                        <option value=\"views\"%s>Views</option>\n", !strcmp(order, "views") ? " selected=\"selected\"" : "");
          138         printf("                        <option value=\"rating\"%s>Rating</option>\n", !strcmp(order, "rating") ? " selected=\"selected\"" : "");
          139         OUT(
          140                 "                </select>\n"
          141                 "        </td>\n"
          142                 "</tr>\n"
          143                 "</table>\n"
          144                 "</form>\n");
          145 }
          146 
          147 void
          148 footer(void)
          149 {
          150         OUT("</body>\n</html>\n");
          151 }
          152 
          153 int
          154 render_search(struct search_response *r)
          155 {
          156         struct item *v;
          157         int n;
          158         size_t i;
          159 
          160         if (pledge("stdio", NULL) == -1) {
          161                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          162                 exit(1);
          163         }
          164 
          165         n = -1;
          166         if (search[0])
          167                 n = snprintf(title, sizeof(title), "Search: \"%s\" sorted by %s", search, order);
          168         else if (channelid[0])
          169                 n = snprintf(title, sizeof(title), "Channel videos: %s", channelid);
          170         else if (userid[0])
          171                 n = snprintf(title, sizeof(title), "User videos: %s", userid);
          172         if (n < 0 || n >= sizeof(title))
          173                 title[0] = '\0';
          174 
          175         header();
          176 
          177         if (r && r->nitems) {
          178                 OUT(
          179                         "<hr/>\n"
          180                         "<table class=\"videos\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n"
          181                         "<tbody>\n");
          182 
          183                 for (i = 0; i < r->nitems; i++) {
          184                         v = &(r->items[i]);
          185 
          186                         OUT("<tr class=\"v\">\n"
          187                         "        <td class=\"thumb\" width=\"120\" align=\"center\">\n");
          188 
          189                         if (v->id[0]) {
          190                                 OUT("                <a href=\"https://www.youtube.com/embed/");
          191                                 xmlencode(v->id);
          192                                 OUT("\"><img src=\"https://i.ytimg.com/vi/");
          193                                 xmlencode(v->id);
          194                                 OUT("/default.jpg\" alt=\"\" height=\"90\" border=\"0\" /></a>\n");
          195                         } else {
          196                                 /* placeholder image */
          197                                 OUT("                <img src=\"https://i.ytimg.com/vi/\" alt=\"\" height=\"90\" border=\"0\" />\n");
          198                         }
          199                         OUT("        </td>\n"
          200                                 "        <td>\n"
          201                                 "                <span class=\"title\">");
          202 
          203                         if (v->id[0]) {
          204                                 OUT("<a href=\"https://www.youtube.com/embed/");
          205                                 xmlencode(v->id);
          206                                 printf("\" accesskey=\"%zu\"", i);
          207 /*                                if (v->shortdescription[0]) {
          208                                         OUT(" title=\"");
          209                                         xmlencode(v->shortdescription);
          210                                         OUT("\"");
          211                                 }*/
          212                                 OUT(">");
          213                         }
          214 
          215                         switch (v->linktype) {
          216                         case Channel:
          217                                 OUT("[Channel] ");
          218                                 xmlencode(v->channeltitle);
          219                                 break;
          220                         case Movie:
          221                                 OUT("[Movie] ");
          222                                 xmlencode(v->title);
          223                                 break;
          224                         case Playlist:
          225                                 OUT("[Playlist] ");
          226                                 xmlencode(v->title);
          227                                 break;
          228                         default:
          229                                 xmlencode(v->title);
          230                                 break;
          231                         }
          232 
          233                         if (v->id[0])
          234                                 OUT("</a>");
          235 
          236                         /* link to video information */
          237                         if (v->id[0]) {
          238                                 OUT(" | <a href=\"?v=");
          239                                 xmlencode(v->id);
          240                                 OUT("\" title=\"More video details\">Details</a>");
          241                         }
          242 
          243                         OUT(
          244                                 "</span><br/>\n"
          245                                 "\t\t<span class=\"channel\">");
          246 
          247                         if (v->channelid[0]) {
          248                                 OUT("<a href=\"?chan=");
          249                                 xmlencode(v->channelid);
          250                                 OUT("\">");
          251                                 xmlencode(v->channeltitle);
          252                                 OUT("</a>");
          253                         } else if (v->userid[0]) {
          254                                 OUT("<a href=\"?user=");
          255                                 xmlencode(v->channelid);
          256                                 OUT("\">");
          257                                 xmlencode(v->channeltitle);
          258                                 OUT("</a>");
          259                         } else {
          260                                 xmlencode(v->channeltitle);
          261                         }
          262 
          263                         if (v->channelid[0] || v->userid[0]) {
          264                                 OUT(" | <a title=\"");
          265                                 xmlencode(v->channeltitle);
          266                                 OUT(" Atom feed\" href=\"https://www.youtube.com/feeds/videos.xml?");
          267                                 if (v->channelid[0]) {
          268                                         OUT("channel_id=");
          269                                         xmlencode(v->channelid);
          270                                 } else if (v->userid[0]) {
          271                                         OUT("user=");
          272                                         xmlencode(v->userid);
          273                                 }
          274                                 OUT("\">Atom feed</a>");
          275                         }
          276 
          277                         OUT("</span><br/>\n");
          278                         if (v->publishedat[0]) {
          279                                 OUT("                <span class=\"publishedat\">Published: ");
          280                                 OUT(v->publishedat);
          281                                 OUT("</span><br/>\n");
          282                         }
          283                         OUT("                <span class=\"stats\">");
          284                         if (v->viewcount[0]) {
          285                                 printnumsep(v->viewcount);
          286                                 OUT(" views");
          287                         }
          288                         OUT(
          289                                 "</span><br/>\n"
          290                                 "        </td>\n"
          291                                 "        <td align=\"right\" class=\"a-r\">\n"
          292                                 "                <span class=\"duration\">");
          293                         OUT(v->duration);
          294                         OUT(
          295                                 "</span>\n"
          296                                 "        </td>\n"
          297                                 "</tr>\n"
          298                                 "<tr class=\"hr\">\n"
          299                                 "        <td colspan=\"3\"><hr/></td>\n"
          300                                 "</tr>\n");
          301                 }
          302                 OUT("</tbody>\n</table>\n");
          303         }
          304 
          305         footer();
          306 
          307         return 0;
          308 }
          309 
          310 int
          311 render_video(struct video_response *r)
          312 {
          313         char buf[256];
          314         int n;
          315 
          316         if (pledge("stdio", NULL) == -1) {
          317                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          318                 exit(1);
          319         }
          320 
          321         n = snprintf(title, sizeof(title), "%s - %s", r->title, r->author);
          322         if (n < 0 || n >= sizeof(title))
          323                 title[0] = '\0';
          324 
          325         header();
          326 
          327         OUT("<hr/>\n");
          328 
          329         OUT("<table class=\"video\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n");
          330         OUT("<tbody>\n");
          331 
          332         OUT("<tr><td colspan=\"2\"><center>\n");
          333         OUT("<a href=\"https://www.youtube.com/embed/");
          334         xmlencode(r->id);
          335         OUT("\"><img src=\"https://i.ytimg.com/vi/");
          336         xmlencode(r->id);
          337         OUT("/hqdefault.jpg\" alt=\"\" border=\"0\" /></a>\n");
          338         OUT("</center><br/></td></tr>\n");
          339 
          340         OUT("<tr><td><b>Title:</b></td><td>");
          341         OUT("<a href=\"https://www.youtube.com/embed/");
          342         xmlencode(r->id);
          343         OUT("\">");
          344         xmlencode(r->title);
          345         OUT("</a></td></tr>\n");
          346 
          347         if (r->lengthseconds > 0) {
          348                 OUT("<tr><td><b>Length:</b></td><td>");
          349                 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf))
          350                         xmlencode(buf);
          351                 OUT("</td></tr>\n");
          352         }
          353 
          354         if (r->author[0]) {
          355                 OUT("<tr><td><b>Channel:</b></td><td>");
          356                 if (r->channelid[0]) {
          357                         OUT("<a href=\"?chan=");
          358                         xmlencode(r->channelid);
          359                         OUT("\">");
          360                         xmlencode(r->author);
          361                         OUT("</a>");
          362                         OUT(": <a href=\"https://www.youtube.com/feeds/videos.xml?channel_id=");
          363                         xmlencode(r->channelid);
          364                         OUT("\">Atom feed</a>");
          365                 } else {
          366                         xmlencode(r->author);
          367                 }
          368                 OUT("</td></tr>\n");
          369         }
          370 
          371         OUT("<tr><td><b>Views:</b></td><td>");
          372         snprintf(buf, sizeof(buf), "%ld", r->viewcount);
          373         printnumsep(buf);
          374         OUT("</td></tr>\n");
          375 
          376         if (r->publishdate[0]) {
          377                 OUT("<tr><td><b>Published:</b></td><td>");
          378                 xmlencode(r->publishdate);
          379                 OUT("</td></tr>\n");
          380         }
          381 
          382         if (r->uploaddate[0]) {
          383                 OUT("<tr><td><b>Uploaded:</b></td><td>");
          384                 xmlencode(r->uploaddate);
          385                 OUT("</td></tr>\n");
          386         }
          387 
          388         if (r->shortdescription[0]) {
          389                 OUT("<tr><td valign=\"top\"><b>Description:&nbsp;</b></td><td><code>");
          390                 xmlencode_multiline(r->shortdescription);
          391                 OUT("</code></td></tr>\n");
          392         }
          393 
          394         OUT("</tbody>\n");
          395         OUT("</table>\n");
          396 
          397         footer();
          398 
          399         return 0;
          400 }
          401 
          402 int
          403 main(void)
          404 {
          405         struct search_response *r = NULL;
          406         struct video_response *vr = NULL;
          407 
          408         if (pledge("stdio dns inet rpath unveil", NULL) == -1 ||
          409             unveil(TLS_CA_CERT_FILE, "r") == -1 ||
          410             unveil(NULL, NULL) == -1) {
          411                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          412                 exit(1);
          413         }
          414 
          415         parsecgi();
          416 
          417         if (rawsearch[0]) {
          418                 r = youtube_search(rawsearch, page, order);
          419         } else if (channelid[0]) {
          420                 r = youtube_channel_videos(channelid);
          421         } else if (userid[0]) {
          422                 r = youtube_user_videos(userid);
          423         } else if (videoid[0]) {
          424                 vr = youtube_video(videoid);
          425                 if (!vr || vr->isfound == 0) {
          426                         OUT("Status: 500 Internal Server Error\r\n\r\n");
          427                         exit(1);
          428                 }
          429                 render_video(vr);
          430                 return 0;
          431         } else {
          432                 goto show;
          433         }
          434         if (!r || r->nitems == 0) {
          435                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          436                 exit(1);
          437         }
          438 
          439 show:
          440         render_search(r);
          441 
          442         return 0;
          443 }