cgi.c - frontends - front-ends for some sites (experiment) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
cgi.c (10387B) | |
--- | |
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(rawsearc… | |
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; … | |
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=\"… | |
117 "<link rel=\"icon\" type=\"image/png\" href=\"/favicon.p… | |
118 "<meta content=\"width=device-width\" name=\"viewport\" … | |
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\" cel… | |
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=\"au… | |
131 " </td>\n" | |
132 " <td nowrap class=\"nowrap\">\n" | |
133 " <input type=\"submit\" value=\"Search\"… | |
134 " <select name=\"o\" title=\"Order by\" a… | |
135 printf(" <option value=\"date\"%s>Creatio… | |
136 printf(" <option value=\"relevance\"%s>Re… | |
137 printf(" <option value=\"views\"%s>Views<… | |
138 printf(" <option value=\"rating\"%s>Ratin… | |
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\" sorte… | |
168 else if (channelid[0]) | |
169 n = snprintf(title, sizeof(title), "Channel videos: %s",… | |
170 else if (userid[0]) | |
171 n = snprintf(title, sizeof(title), "User videos: %s", us… | |
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=\… | |
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… | |
188 | |
189 if (v->id[0]) { | |
190 OUT(" <a href=\"https://w… | |
191 xmlencode(v->id); | |
192 OUT("\"><img src=\"https://i.ytimg.com/v… | |
193 xmlencode(v->id); | |
194 OUT("/default.jpg\" alt=\"\" height=\"90… | |
195 } else { | |
196 /* placeholder image */ | |
197 OUT(" <img src=\"https://… | |
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/e… | |
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\">Det… | |
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.you… | |
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=\"publi… | |
280 OUT(v->publishedat); | |
281 OUT("</span><br/>\n"); | |
282 } | |
283 OUT(" <span class=\"stats\">"); | |
284 if (v->viewcount[0]) { | |
285 if (!printnumsep(v->viewcount)) | |
286 OUT("0"); | |
287 OUT(" views"); | |
288 } | |
289 OUT( | |
290 "</span><br/>\n" | |
291 " </td>\n" | |
292 " <td align=\"right\" class=\"a-r… | |
293 " <span class=\"duration\… | |
294 OUT(v->duration); | |
295 OUT( | |
296 "</span>\n" | |
297 " </td>\n" | |
298 "</tr>\n" | |
299 "<tr class=\"hr\">\n" | |
300 " <td colspan=\"3\"><hr/></td>\n" | |
301 "</tr>\n"); | |
302 } | |
303 OUT("</tbody>\n</table>\n"); | |
304 } | |
305 | |
306 footer(); | |
307 | |
308 return 0; | |
309 } | |
310 | |
311 int | |
312 render_video(struct video_response *r) | |
313 { | |
314 char buf[256]; | |
315 int n; | |
316 | |
317 if (pledge("stdio", NULL) == -1) { | |
318 OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
319 exit(1); | |
320 } | |
321 | |
322 n = snprintf(title, sizeof(title), "%s - %s", r->title, r->autho… | |
323 if (n < 0 || n >= sizeof(title)) | |
324 title[0] = '\0'; | |
325 | |
326 header(); | |
327 | |
328 OUT("<hr/>\n"); | |
329 | |
330 OUT("<table class=\"video\" border=\"0\" cellpadding=\"0\" cells… | |
331 OUT("<tbody>\n"); | |
332 | |
333 OUT("<tr><td colspan=\"2\"><center>\n"); | |
334 OUT("<a href=\"https://www.youtube.com/embed/"); | |
335 xmlencode(r->id); | |
336 OUT("\"><img src=\"https://i.ytimg.com/vi/"); | |
337 xmlencode(r->id); | |
338 OUT("/hqdefault.jpg\" alt=\"\" border=\"0\" /></a>\n"); | |
339 OUT("</center><br/></td></tr>\n"); | |
340 | |
341 OUT("<tr><td><b>Title:</b></td><td>"); | |
342 OUT("<a href=\"https://www.youtube.com/embed/"); | |
343 xmlencode(r->id); | |
344 OUT("\">"); | |
345 xmlencode(r->title); | |
346 OUT("</a></td></tr>\n"); | |
347 | |
348 if (r->lengthseconds > 0) { | |
349 OUT("<tr><td><b>Length:</b></td><td>"); | |
350 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < si… | |
351 xmlencode(buf); | |
352 OUT("</td></tr>\n"); | |
353 } | |
354 | |
355 if (r->author[0]) { | |
356 OUT("<tr><td><b>Channel:</b></td><td>"); | |
357 if (r->channelid[0]) { | |
358 OUT("<a href=\"?chan="); | |
359 xmlencode(r->channelid); | |
360 OUT("\">"); | |
361 xmlencode(r->author); | |
362 OUT("</a>"); | |
363 OUT(": <a href=\"https://www.youtube.com/feeds/v… | |
364 xmlencode(r->channelid); | |
365 OUT("\">Atom feed</a>"); | |
366 } else { | |
367 xmlencode(r->author); | |
368 } | |
369 OUT("</td></tr>\n"); | |
370 } | |
371 | |
372 OUT("<tr><td><b>Views:</b></td><td>"); | |
373 snprintf(buf, sizeof(buf), "%ld", r->viewcount); | |
374 if (!printnumsep(buf)) | |
375 OUT("0"); | |
376 OUT("</td></tr>\n"); | |
377 | |
378 if (r->publishdate[0]) { | |
379 OUT("<tr><td><b>Published:</b></td><td>"); | |
380 xmlencode(r->publishdate); | |
381 OUT("</td></tr>\n"); | |
382 } | |
383 | |
384 if (r->uploaddate[0]) { | |
385 OUT("<tr><td><b>Uploaded:</b></td><td>"); | |
386 xmlencode(r->uploaddate); | |
387 OUT("</td></tr>\n"); | |
388 } | |
389 | |
390 if (r->shortdescription[0]) { | |
391 OUT("<tr><td valign=\"top\"><b>Description: </b></t… | |
392 xmlencode_multiline(r->shortdescription); | |
393 OUT("</code></td></tr>\n"); | |
394 } | |
395 | |
396 OUT("</tbody>\n"); | |
397 OUT("</table>\n"); | |
398 | |
399 footer(); | |
400 | |
401 return 0; | |
402 } | |
403 | |
404 int | |
405 main(void) | |
406 { | |
407 struct search_response *r = NULL; | |
408 struct video_response *vr = NULL; | |
409 | |
410 if (pledge("stdio dns inet rpath unveil", NULL) == -1 || | |
411 unveil(TLS_CA_CERT_FILE, "r") == -1 || | |
412 unveil(NULL, NULL) == -1) { | |
413 OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
414 exit(1); | |
415 } | |
416 | |
417 parsecgi(); | |
418 | |
419 if (rawsearch[0]) { | |
420 r = youtube_search(rawsearch, page, order); | |
421 } else if (channelid[0]) { | |
422 r = youtube_channel_videos(channelid); | |
423 } else if (userid[0]) { | |
424 r = youtube_user_videos(userid); | |
425 } else if (videoid[0]) { | |
426 vr = youtube_video(videoid); | |
427 if (!vr || vr->isfound == 0) { | |
428 OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
429 exit(1); | |
430 } | |
431 render_video(vr); | |
432 return 0; | |
433 } else { | |
434 goto show; | |
435 } | |
436 if (!r || r->nitems == 0) { | |
437 OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
438 exit(1); | |
439 } | |
440 | |
441 show: | |
442 render_search(r); | |
443 | |
444 return 0; | |
445 } |