gopher.c - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       gopher.c (7656B)
       ---
            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 #define OUTTEXT(s) gophertext(stdout, s, strlen(s));
           19 #define OUTLINK(s) gophertext(stdout, s, strlen(s));
           20 
           21 static const char *host = "127.0.0.1", *port = "70", *requestpath = "/";
           22 
           23 void
           24 line(int _type, const char *username, const char *selector)
           25 {
           26         putchar(_type);
           27         OUTTEXT(username);
           28         putchar('\t');
           29         OUTLINK(selector);
           30         printf("\t%s\t%s\r\n", host, port);
           31 }
           32 
           33 void
           34 error(const char *s)
           35 {
           36         line('3', s, "");
           37 }
           38 
           39 void
           40 info(const char *s)
           41 {
           42         line('i', s, "");
           43 }
           44 
           45 void
           46 header(void)
           47 {
           48 }
           49 
           50 void
           51 footer(void)
           52 {
           53         fputs(".\r\n", stdout);
           54 }
           55 
           56 int
           57 render_search(struct search_response *r)
           58 {
           59         struct item *v;
           60         size_t i;
           61 
           62         if (pledge("stdio", NULL) == -1)
           63                 exit(1);
           64 
           65         header();
           66 
           67         for (i = 0; i < r->nitems; i++) {
           68                 v = &(r->items[i]);
           69 
           70                 if (i > 0) {
           71                         info("");
           72                         info("");
           73                 }
           74                 if (v->id[0])
           75                         putchar('h');
           76                 else
           77                         putchar('i');
           78 
           79                 if (v->membersonly)
           80                         OUTTEXT(MEMBERS_ONLY);
           81 
           82                 switch (v->linktype) {
           83                 case Channel:
           84                         OUT("[Channel] ");
           85                         OUTTEXT(v->channeltitle);
           86                         break;
           87                 case Movie:
           88                         OUT("[Movie] ");
           89                         OUTTEXT(v->title);
           90                         break;
           91                 case Playlist:
           92                         OUT("[Playlist] ");
           93                         OUTTEXT(v->title);
           94                         break;
           95                 default:
           96                         OUTTEXT(v->title);
           97                         break;
           98                 }
           99 
          100                 OUT("\t");
          101                 if (v->id[0]) {
          102                         OUT("URL:https://www.youtube.com/embed/");
          103                         OUTLINK(v->id);
          104                 }
          105                 printf("\t%s\t%s\r\n", host, port);
          106 
          107                 if (v->id[0]) {
          108                         printf("1Details\t%s?v=", requestpath);
          109                         OUTLINK(v->id);
          110                         printf("\t%s\t%s\r\n", host, port);
          111                 }
          112 
          113                 if (v->channelid[0]) {
          114                         OUT("1");
          115                         OUTTEXT(v->channeltitle);
          116                         printf("\t%s?c=", requestpath);
          117                         OUTLINK(v->channelid);
          118                         printf("\t%s\t%s\r\n", host, port);
          119                 } else if (v->userid[0]) {
          120                         OUT("1");
          121                         OUTTEXT(v->channeltitle);
          122                         printf("\t%s?u=", requestpath);
          123                         OUTLINK(v->userid);
          124                         printf("\t%s\t%s\r\n", host, port);
          125                 } else if (v->channeltitle[0]) {
          126                         OUT("i");
          127                         OUTTEXT(v->channeltitle);
          128                         printf("\t%s\t%s\t%s\r\n", "", host, port);
          129                 }
          130 
          131                 if (v->channelid[0] || v->userid[0]) {
          132                         OUT("hAtom feed of ");
          133                         OUTTEXT(v->channeltitle);
          134                         OUT("\t");
          135                         OUTLINK("URL:https://www.youtube.com/feeds/videos.xml?");
          136                         if (v->channelid[0]) {
          137                                 OUT("channel_id=");
          138                                 OUTLINK(v->channelid);
          139                         } else if (v->userid[0]) {
          140                                 OUT("user=");
          141                                 OUTLINK(v->userid);
          142                         }
          143                         printf("\t%s\t%s\r\n", host, port);
          144                 }
          145                 if (v->duration[0]) {
          146                         OUT("iDuration:      " );
          147                         OUTTEXT(v->duration);
          148                         printf("\t%s\t%s\t%s\r\n", "", host, port);
          149                 }
          150                 if (v->publishedat[0]) {
          151                         OUT("iPublished:     ");
          152                         OUTTEXT(v->publishedat);
          153                         printf("\t%s\t%s\t%s\r\n", "", host, port);
          154                 }
          155                 if (v->viewcount[0]) {
          156                         OUT("iViews:         ");
          157                         if (!printnumsep(v->viewcount))
          158                                 OUT("0");
          159                         printf("\t%s\t%s\t%s\r\n", "", host, port);
          160                 }
          161         }
          162         footer();
          163 
          164         return 0;
          165 }
          166 
          167 int
          168 render_video(struct video_response *r)
          169 {
          170         char buf[256];
          171         const char *s, *e;
          172         /* softwrap: try to wrap on word boundary, otherwise hardwrap */
          173         int i, sw = 72, hw = 9999;
          174 
          175         if (pledge("stdio", NULL) == -1)
          176                 exit(1);
          177 
          178         header();
          179 
          180         info("");
          181 
          182         OUT("hTitle:     ");
          183         OUTTEXT(r->title);
          184         OUT("\tURL:https://www.youtube.com/embed/");
          185         OUTTEXT(r->id);
          186         printf("\t%s\t%s\r\n", host, port);
          187 
          188         OUT("hURL:       ");
          189         OUT("https://www.youtube.com/embed/");
          190         OUTTEXT(r->id);
          191         OUT("\tURL:");
          192         OUT("https://www.youtube.com/embed/");
          193         OUTTEXT(r->id);
          194         printf("\t%s\t%s\r\n", host, port);
          195 
          196         if (r->lengthseconds > 0) {
          197                 OUT("iLength:    ");
          198                 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf))
          199                         OUTTEXT(buf);
          200                 printf("\t%s\t%s\t%s\r\n", "", host, port);
          201         }
          202 
          203         /* show playability state and reason: for example when it is a members-only video */
          204         if (r->playabilityreason[0]) {
          205                 OUT("iStatus:    ");
          206                 if (r->playabilitystatus[0]) {
          207                         OUTTEXT(r->playabilitystatus);
          208                         OUTTEXT(": ");
          209                 }
          210                 OUTTEXT(r->playabilityreason);
          211                 printf("\t%s\t%s\t%s\r\n", "", host, port);
          212         }
          213 
          214         OUT("hThumbnail: https://i.ytimg.com/vi/");
          215         OUTTEXT(r->id);
          216         OUT("/hqdefault.jpg\tURL:https://i.ytimg.com/vi/");
          217         OUTLINK(r->id);
          218         printf("/hqdefault.jpg\t%s\t%s\r\n", host, port);
          219 
          220         if (r->author[0]) {
          221                 OUT("1Channel:   ");
          222                 OUTTEXT(r->author);
          223                 printf("\t%s?c=%s\t%s\t%s\r\n", requestpath, r->channelid, host, port);
          224 
          225                 if (r->channelid[0]) {
          226                         OUT("hAtom feed\tURL:https://www.youtube.com/feeds/videos.xml?channel_id=");
          227                         OUTTEXT(r->channelid);
          228                         printf("\t%s\t%s\r\n", host, port);
          229                 }
          230         }
          231 
          232         OUT("iViews:     ");
          233         snprintf(buf, sizeof(buf), "%ld", r->viewcount);
          234         if (!printnumsep(buf))
          235                 OUT("0");
          236         printf("\t%s\t%s\t%s\r\n", "", host, port);
          237 
          238         if (r->publishdate[0]) {
          239                 OUT("iPublished: ");
          240                 OUTTEXT(r->publishdate);
          241                 printf("\t%s\t%s\t%s\r\n", "", host, port);
          242         }
          243 
          244         if (r->uploaddate[0]) {
          245                 OUT("iUploaded:  ");
          246                 OUTTEXT(r->uploaddate);
          247                 printf("\t%s\t%s\t%s\r\n", "", host, port);
          248         }
          249 
          250         if (r->shortdescription[0]) {
          251                 info("");
          252                 info("");
          253 
          254                 /* multi-line text */
          255                 i = 0;
          256                 for (s = e = r->shortdescription; ; e++) {
          257                         if (!i)
          258                                 putchar('i');
          259                         if (*e == '\n' || *e == '\0' ||
          260                            i > hw || (i > sw && (*e == ' ' || *e == '\t'))) {
          261                                 i = 0;
          262                                 gophertext(stdout, s, e - s);
          263                                 printf("\t%s\t%s\t%s\r\n", "", host, port);
          264                                 if (*e == '\0')
          265                                         break;
          266                                 s = e + 1;
          267                         } else {
          268                                 /* start of rune, wrongly assume 1 rune is 1 column for now */
          269                                 if (!((unsigned char)*e & 0x80))
          270                                         i++;
          271                         }
          272                 }
          273         }
          274 
          275         footer();
          276 
          277         return 0;
          278 }
          279 
          280 int
          281 main(void)
          282 {
          283         struct search_response *r = NULL;
          284         struct video_response *vr = NULL;
          285         const char *channelid = "", *userid = "", *videoid = "";
          286         const char *querystring = "", *rawsearch = "", *p;
          287         char search[1024] = "";
          288 
          289         if (pledge("stdio dns inet rpath unveil", NULL) == -1)
          290                 exit(1);
          291         if (unveil(TLS_CA_CERT_FILE, "r") == -1 ||
          292             unveil(NULL, NULL) == -1)
          293                 exit(1);
          294 
          295         if ((p = getenv("SERVER_NAME")))
          296                 host = p;
          297         if ((p = getenv("SERVER_PORT")))
          298                 port = p;
          299         if ((p = getenv("PATH_TRANSLATED")))
          300                 requestpath = p;
          301         else if ((p = getenv("PATH_INFO"))) /* fallback to PATH_INFO */
          302                 requestpath = p;
          303         if ((p = getenv("QUERY_STRING")))
          304                 querystring = p;
          305 
          306         p = NULL;
          307         if (querystring[0] == '?')
          308                 querystring++;
          309         if (querystring[0] == 'c' && querystring[1] == '=') {
          310                 channelid = querystring + 2;
          311                 p = querystring = "";
          312         } else if (querystring[0] == 'u' && querystring[1] == '=') {
          313                 userid = querystring + 2;
          314                 p = querystring = "";
          315         } else if (querystring[0] == 'v' && querystring[1] == '=') {
          316                 videoid = querystring + 2;
          317                 p = querystring = "";
          318         }
          319 
          320         if (querystring[0])
          321                 p = querystring;
          322         if (!p)
          323                 p = getenv("X_GOPHER_SEARCH"); /* geomyidae */
          324         if (!p)
          325                 p = getenv("SEARCHREQUEST"); /* gophernicus */
          326         if (p)
          327                 rawsearch = p;
          328         if (p && !uriencode(p, search, sizeof(search))) {
          329                 error("Invalid search");
          330                 footer();
          331                 return 0;
          332         }
          333 
          334         OUT("7Search");
          335         if (rawsearch[0]) {
          336                 OUT(": ");
          337                 OUT(rawsearch);
          338         }
          339         printf("\t%s\t%s\t%s\r\n", requestpath, host, port);
          340 
          341         info("");
          342         line('1', "<- back to main directory", requestpath);
          343 
          344         if (search[0]) {
          345                 r = youtube_search(search, "", "relevance");
          346         } else if (channelid[0]) {
          347                 r = youtube_channel_videos(channelid);
          348         } else if (userid[0]) {
          349                 r = youtube_user_videos(userid);
          350         } else if (videoid[0]) {
          351                 vr = youtube_video(videoid);
          352                 if (!vr || vr->isfound == 0) {
          353                         error("No video found");
          354                         footer();
          355                         return 0;
          356                 }
          357                 render_video(vr);
          358                 return 0;
          359         } else {
          360                 footer();
          361                 return 0;
          362         }
          363         info("");
          364 
          365         if (!r || r->nitems == 0) {
          366                 error("No videos found");
          367                 footer();
          368                 return 0;
          369         }
          370 
          371         render_search(r);
          372 
          373         return 0;
          374 }