cgi.c - frontends - front-ends for some sites (experiment) (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) LICENSE --- cgi.c (10345B) --- 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; 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 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\">Details</a>"); 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.youtube.com/feeds/videos.xml?"); 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=\"publishedat\">Published: "); 280 OUT(v->publishedat); 281 OUT("</span><br/>\n"); 282 } 283 OUT(" <span class=\"stats\">"); 284 if (v->viewcount[0]) { 285 printnumsep(v->viewcount); 286 OUT(" views"); 287 } 288 OUT( 289 "</span><br/>\n" 290 " </td>\n" 291 " <td align=\"right\" class=\"a-r\">\n" 292 " <span class=\"duration\">"); 293 OUT(v->duration); 294 OUT( 295 "</span>\n" 296 " </td>\n" 297 "</tr>\n" 298 "<tr class=\"hr\">\n" 299 " <td colspan=\"3\"><hr/></td>\n" 300 "</tr>\n"); 301 } 302 OUT("</tbody>\n</table>\n"); 303 } 304 305 footer(); 306 307 return 0; 308 } 309 310 int 311 render_video(struct video_response *r) 312 { 313 char buf[256]; 314 int n; 315 316 if (pledge("stdio", NULL) == -1) { 317 OUT("Status: 500 Internal Server Error\r\n\r\n"); 318 exit(1); 319 } 320 321 n = snprintf(title, sizeof(title), "%s - %s", r->title, r->author); 322 if (n < 0 || n >= sizeof(title)) 323 title[0] = '\0'; 324 325 header(); 326 327 OUT("<hr/>\n"); 328 329 OUT("<table class=\"video\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n"); 330 OUT("<tbody>\n"); 331 332 OUT("<tr><td colspan=\"2\"><center>\n"); 333 OUT("<a href=\"https://www.youtube.com/embed/"); 334 xmlencode(r->id); 335 OUT("\"><img src=\"https://i.ytimg.com/vi/"); 336 xmlencode(r->id); 337 OUT("/hqdefault.jpg\" alt=\"\" border=\"0\" /></a>\n"); 338 OUT("</center><br/></td></tr>\n"); 339 340 OUT("<tr><td><b>Title:</b></td><td>"); 341 OUT("<a href=\"https://www.youtube.com/embed/"); 342 xmlencode(r->id); 343 OUT("\">"); 344 xmlencode(r->title); 345 OUT("</a></td></tr>\n"); 346 347 if (r->lengthseconds > 0) { 348 OUT("<tr><td><b>Length:</b></td><td>"); 349 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf)) 350 xmlencode(buf); 351 OUT("</td></tr>\n"); 352 } 353 354 if (r->author[0]) { 355 OUT("<tr><td><b>Channel:</b></td><td>"); 356 if (r->channelid[0]) { 357 OUT("<a href=\"?chan="); 358 xmlencode(r->channelid); 359 OUT("\">"); 360 xmlencode(r->author); 361 OUT("</a>"); 362 OUT(": <a href=\"https://www.youtube.com/feeds/videos.xml?channel_id="); 363 xmlencode(r->channelid); 364 OUT("\">Atom feed</a>"); 365 } else { 366 xmlencode(r->author); 367 } 368 OUT("</td></tr>\n"); 369 } 370 371 OUT("<tr><td><b>Views:</b></td><td>"); 372 snprintf(buf, sizeof(buf), "%ld", r->viewcount); 373 printnumsep(buf); 374 OUT("</td></tr>\n"); 375 376 if (r->publishdate[0]) { 377 OUT("<tr><td><b>Published:</b></td><td>"); 378 xmlencode(r->publishdate); 379 OUT("</td></tr>\n"); 380 } 381 382 if (r->uploaddate[0]) { 383 OUT("<tr><td><b>Uploaded:</b></td><td>"); 384 xmlencode(r->uploaddate); 385 OUT("</td></tr>\n"); 386 } 387 388 if (r->shortdescription[0]) { 389 OUT("<tr><td valign=\"top\"><b>Description: </b></td><td><code>"); 390 xmlencode_multiline(r->shortdescription); 391 OUT("</code></td></tr>\n"); 392 } 393 394 OUT("</tbody>\n"); 395 OUT("</table>\n"); 396 397 footer(); 398 399 return 0; 400 } 401 402 int 403 main(void) 404 { 405 struct search_response *r = NULL; 406 struct video_response *vr = NULL; 407 408 if (pledge("stdio dns inet rpath unveil", NULL) == -1 || 409 unveil(TLS_CA_CERT_FILE, "r") == -1 || 410 unveil(NULL, NULL) == -1) { 411 OUT("Status: 500 Internal Server Error\r\n\r\n"); 412 exit(1); 413 } 414 415 parsecgi(); 416 417 if (rawsearch[0]) { 418 r = youtube_search(rawsearch, page, order); 419 } else if (channelid[0]) { 420 r = youtube_channel_videos(channelid); 421 } else if (userid[0]) { 422 r = youtube_user_videos(userid); 423 } else if (videoid[0]) { 424 vr = youtube_video(videoid); 425 if (!vr || vr->isfound == 0) { 426 OUT("Status: 500 Internal Server Error\r\n\r\n"); 427 exit(1); 428 } 429 render_video(vr); 430 return 0; 431 } else { 432 goto show; 433 } 434 if (!r || r->nitems == 0) { 435 OUT("Status: 500 Internal Server Error\r\n\r\n"); 436 exit(1); 437 } 438 439 show: 440 render_search(r); 441 442 return 0; 443 }