cgi.c - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       cgi.c (10744B)
       ---
            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, len;
          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                         if (v->membersonly)
          216                                 xmlencode(MEMBERS_ONLY);
          217 
          218                         switch (v->linktype) {
          219                         case Channel:
          220                                 OUT("[Channel] ");
          221                                 xmlencode(v->channeltitle);
          222                                 break;
          223                         case Movie:
          224                                 OUT("[Movie] ");
          225                                 xmlencode(v->title);
          226                                 break;
          227                         case Playlist:
          228                                 OUT("[Playlist] ");
          229                                 xmlencode(v->title);
          230                                 break;
          231                         default:
          232                                 xmlencode(v->title);
          233                                 break;
          234                         }
          235 
          236                         if (v->id[0])
          237                                 OUT("</a>");
          238 
          239                         /* link to video information */
          240                         if (v->id[0]) {
          241                                 OUT(" | <a href=\"?v=");
          242                                 xmlencode(v->id);
          243                                 OUT("\" title=\"More video details\">Details</a>");
          244                         }
          245 
          246                         OUT(
          247                                 "</span><br/>\n"
          248                                 "\t\t<span class=\"channel\">");
          249 
          250                         if (v->channelid[0]) {
          251                                 OUT("<a href=\"?chan=");
          252                                 xmlencode(v->channelid);
          253                                 OUT("\">");
          254                                 xmlencode(v->channeltitle);
          255                                 OUT("</a>");
          256                         } else if (v->userid[0]) {
          257                                 OUT("<a href=\"?user=");
          258                                 xmlencode(v->channelid);
          259                                 OUT("\">");
          260                                 xmlencode(v->channeltitle);
          261                                 OUT("</a>");
          262                         } else {
          263                                 xmlencode(v->channeltitle);
          264                         }
          265 
          266                         if (v->channelid[0] || v->userid[0]) {
          267                                 OUT(" | <a title=\"");
          268                                 xmlencode(v->channeltitle);
          269                                 OUT(" Atom feed\" href=\"https://www.youtube.com/feeds/videos.xml?");
          270                                 if (v->channelid[0]) {
          271                                         OUT("channel_id=");
          272                                         xmlencode(v->channelid);
          273                                 } else if (v->userid[0]) {
          274                                         OUT("user=");
          275                                         xmlencode(v->userid);
          276                                 }
          277                                 OUT("\">Atom feed</a>");
          278                         }
          279 
          280                         OUT("</span><br/>\n");
          281                         if (v->publishedat[0]) {
          282                                 OUT("                <span class=\"publishedat\">Published: ");
          283                                 OUT(v->publishedat);
          284                                 OUT("</span><br/>\n");
          285                         }
          286                         OUT("                <span class=\"stats\">");
          287                         if (v->viewcount[0]) {
          288                                 if (!printnumsep(v->viewcount))
          289                                         OUT("0");
          290                                 OUT(" views");
          291                         }
          292                         OUT(
          293                                 "</span><br/>\n"
          294                                 "        </td>\n"
          295                                 "        <td align=\"right\" class=\"a-r\">\n"
          296                                 "                <span class=\"duration\">");
          297                         OUT(v->duration);
          298                         OUT(
          299                                 "</span>\n"
          300                                 "        </td>\n"
          301                                 "</tr>\n"
          302                                 "<tr class=\"hr\">\n"
          303                                 "        <td colspan=\"3\"><hr/></td>\n"
          304                                 "</tr>\n");
          305                 }
          306                 OUT("</tbody>\n</table>\n");
          307         }
          308 
          309         footer();
          310 
          311         return 0;
          312 }
          313 
          314 int
          315 render_video(struct video_response *r)
          316 {
          317         char buf[256];
          318         int n;
          319 
          320         if (pledge("stdio", NULL) == -1) {
          321                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          322                 exit(1);
          323         }
          324 
          325         n = snprintf(title, sizeof(title), "%s - %s", r->title, r->author);
          326         if (n < 0 || n >= sizeof(title))
          327                 title[0] = '\0';
          328 
          329         header();
          330 
          331         OUT("<hr/>\n");
          332 
          333         OUT("<table class=\"video\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n");
          334         OUT("<tbody>\n");
          335 
          336         OUT("<tr><td colspan=\"2\"><center>\n");
          337         OUT("<a href=\"https://www.youtube.com/embed/");
          338         xmlencode(r->id);
          339         OUT("\"><img src=\"https://i.ytimg.com/vi/");
          340         xmlencode(r->id);
          341         OUT("/hqdefault.jpg\" alt=\"\" border=\"0\" /></a>\n");
          342         OUT("</center><br/></td></tr>\n");
          343 
          344         OUT("<tr><td><b>Title:</b></td><td>");
          345         OUT("<a href=\"https://www.youtube.com/embed/");
          346         xmlencode(r->id);
          347         OUT("\">");
          348         xmlencode(r->title);
          349         OUT("</a></td></tr>\n");
          350 
          351         if (r->lengthseconds > 0) {
          352                 OUT("<tr><td><b>Length:</b></td><td>");
          353                 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf))
          354                         xmlencode(buf);
          355                 OUT("</td></tr>\n");
          356         }
          357 
          358         /* show playability state and reason: for example when it is a members-only video */
          359         if (r->playabilityreason[0]) {
          360                 OUT("<tr><td><b>Status:</b></td><td><b>");
          361                 if (r->playabilitystatus[0]) {
          362                         OUT(r->playabilitystatus);
          363                         OUT(": ");
          364                 }
          365                 OUT(r->playabilityreason);
          366                 OUT("</b></td></tr>\n");
          367         }
          368 
          369         if (r->author[0]) {
          370                 OUT("<tr><td><b>Channel:</b></td><td>");
          371                 if (r->channelid[0]) {
          372                         OUT("<a href=\"?chan=");
          373                         xmlencode(r->channelid);
          374                         OUT("\">");
          375                         xmlencode(r->author);
          376                         OUT("</a>");
          377                         OUT(": <a href=\"https://www.youtube.com/feeds/videos.xml?channel_id=");
          378                         xmlencode(r->channelid);
          379                         OUT("\">Atom feed</a>");
          380                 } else {
          381                         xmlencode(r->author);
          382                 }
          383                 OUT("</td></tr>\n");
          384         }
          385 
          386         OUT("<tr><td><b>Views:</b></td><td>");
          387         snprintf(buf, sizeof(buf), "%ld", r->viewcount);
          388         if (!printnumsep(buf))
          389                 OUT("0");
          390         OUT("</td></tr>\n");
          391 
          392         if (r->publishdate[0]) {
          393                 OUT("<tr><td><b>Published:</b></td><td>");
          394                 xmlencode(r->publishdate);
          395                 OUT("</td></tr>\n");
          396         }
          397 
          398         if (r->uploaddate[0]) {
          399                 OUT("<tr><td><b>Uploaded:</b></td><td>");
          400                 xmlencode(r->uploaddate);
          401                 OUT("</td></tr>\n");
          402         }
          403 
          404         if (r->shortdescription[0]) {
          405                 OUT("<tr><td valign=\"top\"><b>Description:&nbsp;</b></td><td><code>");
          406                 xmlencode_multiline(r->shortdescription);
          407                 OUT("</code></td></tr>\n");
          408         }
          409 
          410         OUT("</tbody>\n");
          411         OUT("</table>\n");
          412 
          413         footer();
          414 
          415         return 0;
          416 }
          417 
          418 int
          419 main(void)
          420 {
          421         struct search_response *r = NULL;
          422         struct video_response *vr = NULL;
          423 
          424         if (pledge("stdio dns inet rpath unveil", NULL) == -1 ||
          425             unveil(TLS_CA_CERT_FILE, "r") == -1 ||
          426             unveil(NULL, NULL) == -1) {
          427                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          428                 exit(1);
          429         }
          430 
          431         parsecgi();
          432 
          433         if (rawsearch[0]) {
          434                 r = youtube_search(rawsearch, page, order);
          435         } else if (channelid[0]) {
          436                 r = youtube_channel_videos(channelid);
          437         } else if (userid[0]) {
          438                 r = youtube_user_videos(userid);
          439         } else if (videoid[0]) {
          440                 vr = youtube_video(videoid);
          441                 if (!vr || vr->isfound == 0) {
          442                         OUT("Status: 500 Internal Server Error\r\n\r\n");
          443                         exit(1);
          444                 }
          445                 render_video(vr);
          446                 return 0;
          447         } else {
          448                 goto show;
          449         }
          450         if (!r || r->nitems == 0) {
          451                 OUT("Status: 500 Internal Server Error\r\n\r\n");
          452                 exit(1);
          453         }
          454 
          455 show:
          456         render_search(r);
          457 
          458         return 0;
          459 }