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("<", stdout); break;
37 case '>': fputs(">", stdout); break;
38 case '\'': fputs("'", stdout); break;
39 case '&': fputs("&", stdout); break;
40 case '"': fputs(""", 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: </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 }