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 }