cgi.c - frontends - front-ends for some sites (experiment)
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
cgi.c (9039B)
---
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 <time.h>
12 #include <unistd.h>
13
14 #include "reddit.h"
15
16 #define OUT(s) (fputs((s), stdout))
17
18 extern char **environ;
19
20 static struct list_response *response;
21 static time_t now;
22
23 /* CGI parameters */
24 static char rawsearch[4096], search[4096];
25 static char subreddit[1024], mode[16], slimit[32], order[16];
26 static long limit = 100;
27
28 void
29 parsecgi(void)
30 {
31 char *query, *p;
32 size_t len;
33
34 if (!(query = getenv("QUERY_STRING")))
35 query = "";
36
37 /* subreddit: select subreddit */
38 if ((p = getparam(query, "subreddit"))) {
39 if (decodeparam(subreddit, sizeof(subreddit), p) == -1)
40 subreddit[0] = '\0';
41 }
42
43 /* order */
44 if ((p = getparam(query, "o"))) {
45 if (decodeparam(order, sizeof(order), p) == -1 ||
46 (strcmp(order, "hot") &&
47 strcmp(order, "new") &&
48 strcmp(order, "rising") &&
49 strcmp(order, "controversial")))
50 order[0] = '\0';
51 }
52 if (!order[0])
53 snprintf(order, sizeof(order), "hot");
54
55 #if 0
56 /* TODO */
57 /* page */
58 if ((p = getparam(query, "page"))) {
59 if (decodeparam(page, sizeof(page), p) == -1)
60 page[0] = '\0';
61 /* check if it's a number >= 0 and < 100 */
62 errno = 0;
63 curpage = strtol(page, NULL, 10);
64 if (errno || curpage < 0 || curpage > 100) {
65 curpage = 1;
66 page[0] = '\0';
67 }
68 }
69 #endif
70
71 /* limit */
72 if ((p = getparam(query, "limit"))) {
73 if (decodeparam(slimit, sizeof(slimit), p) == -1)
74 slimit[0] = '\0';
75 /* check if it's a number > 0 and < 100 */
76 errno = 0;
77 limit = strtol(slimit, NULL, 10);
78 if (errno || limit <= 0 || limit > 100) {
79 limit = 100; /* default */
80 slimit[0] = '\0';
81 }
82 }
83
84 /* mode */
85 if ((p = getparam(query, "m"))) {
86 if (decodeparam(mode, sizeof(mode), p) != -1) {
87 /* fixup first character (label) for matching */
88 if (mode[0])
89 mode[0] = tolower((unsigned char)mode[0]);
90 /* allowed themes */
91 if (strcmp(mode, "light") &&
92 strcmp(mode, "dark") &&
93 strcmp(mode, "pink") &&
94 strcmp(mode, "templeos"))
95 mode[0] = '\0';
96 }
97 }
98 if (!mode[0])
99 snprintf(mode, sizeof(mode), "light");
100
101 /* search */
102 if ((p = getparam(query, "q"))) {
103 if ((len = strcspn(p, "&")) && len + 1 < sizeof(rawsearch)) {
104 memcpy(rawsearch, p, len);
105 rawsearch[len] = '\0';
106 }
107
108 if (decodeparam(search, sizeof(search), p) == -1) {
109 OUT("Status: 401 Bad Request\r\n\r\n");
110 exit(1);
111 }
112 }
113 }
114
115 int
116 render(struct list_response *r)
117 {
118 struct item *items = r->items, *item;
119 char tmp[64], timebuf[32], baseurl[256];
120 char *start, *end;
121 int i;
122
123 #if 0
124 if (pledge("stdio", NULL) == -1) {
125 OUT("Status: 500 Internal Server Error\r\n\r\n");
126 exit(1);
127 }
128 #endif
129
130 OUT(
131 "Content-Type: text/html; charset=utf-8\r\n\r\n"
132 "<!DOCTYPE html>\n<html>\n<head>\n"
133 "<meta name=\"referrer\" content=\"no-referrer\" />\n"
134 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
135 "<title>");
136 if (r->nitems && subreddit[0]) {
137 OUT("r/");
138 xmlencode(subreddit);
139 OUT(" - Reddit");
140 } else {
141 OUT("Reddit");
142 }
143 OUT("</title>\n");
144 OUT(
145 "<link rel=\"stylesheet\" href=\"css/");
146 xmlencode(mode);
147 OUT(
148 ".css\" type=\"text/css\" media=\"screen\" />\n"
149 "<link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />\n"
150 "<meta content=\"width=device-width\" name=\"viewport\" />\n"
151 "</head>\n"
152 "<body class=\"search\">\n"
153 "<form method=\"get\" action=\"\">\n");
154
155 OUT("<input type=\"hidden\" name=\"m\" value=\"");
156 xmlencode(mode);
157 OUT("\" />\n");
158 if (subreddit[0]) {
159 OUT("<input type=\"hidden\" name=\"subreddit\" value=\"");
160 xmlencode(subreddit);
161 OUT("\" />\n");
162 }
163 OUT("<input type=\"hidden\" name=\"limit\" value=\"");
164 xmlencode("100"); /* TODO maybe */
165 OUT("\" />\n");
166 OUT("<input type=\"hidden\" name=\"order\" value=\"");
167 xmlencode("hot"); /* TODO maybe */
168 OUT("\" />\n");
169
170 OUT(
171 "<table class=\"search\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n"
172 "<tr>\n"
173 "<td width=\"100%\" class=\"input\">\n"
174 " <input type=\"search\" name=\"q\" value=\"\" placeholder=\"Search...\" size=\"72\" autofocus=\"autofocus\" class=\"search\" accesskey=\"f\" />\n"
175 "</td>\n"
176 "<td nowrap class=\"nowrap\">\n"
177 " <input type=\"submit\" value=\"Search\" class=\"button\"/>\n"
178 " <select name=\"o\" title=\"Order by\" accesskey=\"o\">\n"
179 " <option value=\"hot\" selected=\"selected\">Hot</option>\n"
180 " <option value=\"new\">New</option>\n"
181 " <option value=\"rising\">Rising</option>\n"
182 " <option value=\"controversial\">Controversial</option>\n"
183 " </select>\n"
184 " <label for=\"m\">Style: </label>\n");
185
186 if (!strcmp(mode, "light"))
187 OUT("\t\t<input type=\"submit\" name=\"m\" value=\"Dark\" title=\"Dark mode\" id=\"m\" accesskey=\"s\"/>\n");
188 else
189 OUT("\t\t<input type=\"submit\" name=\"m\" value=\"Light\" title=\"Light mode\" id=\"m\" accesskey=\"s\"/>\n");
190
191 OUT(
192 " </td>\n"
193 "</tr>\n"
194 "</table>\n"
195 "</form>\n");
196
197 if (r->nitems) {
198 OUT(
199 "<hr/>\n"
200 "<table class=\"items\" width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n"
201 "<tbody>\n");
202
203 for (i = 0; i < r->nitems; i++) {
204 item = r->items + i;
205
206 OUT(
207 "<tr>\n"
208 "<td valign=\"middle\" align=\"center\" class=\"score\" width=\"100\">\n"
209 " \n");
210 printf("%zu", item->ups); /* upvotes / score */
211 OUT(
212 "</td>\n"
213 "<td valign=\"middle\" align=\"center\" class=\"thumb\" width=\"140\">\n");
214
215 if (item->thumbnail[0] && !strncmp(item->thumbnail, "https://", 8)) {
216 OUT("<a href=\"");
217
218 /* link directly to dash url for video */
219 if (item->is_video)
220 xmlencode(item->dash_url);
221 else
222 xmlencode(item->url);
223
224 OUT("\"><img src=\"");
225 xmlencode(item->thumbnail);
226 OUT("\" width=\"140\" alt=\"\" /></a>");
227 }
228
229 OUT(
230 "</td>\n"
231 "<td valign=\"top\">\n"
232 " <h2><a href=\"");
233 /* link directly to dash url for video */
234 if (item->is_video)
235 xmlencode(item->dash_url);
236 else
237 xmlencode(item->url);
238 OUT("\">");
239
240 /* base url of url: somesite.org */
241 baseurl[0] = '\0';
242 if (!item->is_video && item->url[0]) {
243 if ((start = strstr(item->url, "://"))) {
244 start += strlen("://");
245 if ((end = strstr(start, "/"))) {
246 if (end - start + 1 < sizeof(baseurl)) {
247 memcpy(baseurl, start, end - start);
248 baseurl[end - start] = '\0';
249 }
250 }
251 }
252 }
253
254 if (item->is_video)
255 OUT("[video] ");
256 xmlencode(item->title);
257
258 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M", &(item->created_tm));
259
260 OUT("</a></h2>\n");
261 OUT(
262 " <time datetime=\"");
263 OUT(timebuf);
264 OUT("\">Submitted ");
265 if (!friendlytime(item->created_utc)) {
266 OUT("on ");
267 OUT(timebuf);
268 }
269 OUT(" by</time>\n"
270 " <a href=\"\">");
271 xmlencode(item->author);
272 OUT("</a>\n");
273
274 /* global view: show subreddit */
275 if (!subreddit[0]) {
276 /* TODO: link */
277 OUT(" in r/");
278 xmlencode(item->subreddit);
279 }
280
281 OUT(
282 " <br/>\n"
283 " <br/>\n"
284 " <a href=\"https://old.reddit.com/");
285 xmlencode(item->permalink);
286 OUT("\">");
287
288 printf("%zu", item->num_comments);
289
290 OUT(" comments</a>\n"
291 "</td>\n"
292 "<td valign=\"top\" align=\"right\" class=\"tags\">\n"
293 " <span class=\"base\">");
294 OUT(baseurl);
295 OUT(
296 "</span>\n"
297 " <br/>\n"
298 " <span class=\"tag\">");
299
300 /* TODO: + flair color */
301 xmlencode(item->link_flair_text);
302 OUT(" </span>\n"
303 "</td>\n"
304 "</tr>\n");
305
306 OUT("<tr><td colspan=\"4\"><hr/></td></tr>\n");
307 }
308 OUT("</tbody>\n");
309
310 /* pagination does not work for user/channel search */
311 if (r->before[0] || r->after[0]) {
312 OUT(
313 "<tfoot>\n"
314 "<tr>\n"
315 "\t<td align=\"left\" class=\"nowrap\" nowrap>\n");
316 if (r->before[0]) {
317 OUT("\t\t<a href=\"?q=");
318 xmlencode(""); // TODO: remove q param later
319 OUT("&before=");
320 xmlencode(r->before);
321 OUT("&m=");
322 xmlencode(mode);
323 OUT("\" rel=\"prev\" accesskey=\"p\">← prev</a>\n");
324 }
325 if (r->after[0]) {
326 OUT(
327 "\t</td>\n\t<td colspan=\"2\"></td>\n"
328 "\t<td align=\"right\" class=\"a-r nowrap\" nowrap>\n");
329 OUT("\t\t<a href=\"?q="); // TODO: remove q param later.
330 xmlencode(""); //
331 OUT("&after=");
332 xmlencode(r->after);
333 OUT("&m=");
334 xmlencode(mode);
335 OUT("\" rel=\"next\" accesskey=\"n\">next →</a>\n");
336 }
337
338 OUT(
339 "\t</td>\n"
340 "</tr>\n"
341 "</tfoot>\n");
342 }
343 OUT("</table>\n");
344 }
345
346 OUT("</body>\n</html>\n");
347
348 return 0;
349 }
350
351 int
352 main(void)
353 {
354 struct list_response *r;
355
356 #if 0
357 if (pledge("stdio dns inet rpath unveil", NULL) == -1 ||
358 unveil(TLS_CA_CERT_FILE, "r") == -1 ||
359 unveil(NULL, NULL) == -1) {
360 OUT("Status: 500 Internal Server Error\r\n\r\n");
361 exit(1);
362 }
363 #endif
364
365 parsecgi();
366
367 #if 0
368 if (!rawsearch[0] && !chan[0] && !user[0])
369 goto show;
370 #endif
371
372 r = reddit_list(subreddit, ""); // TODO: page
373 if (!r || r->nitems <= 0) {
374 OUT("Status: 500 Internal Server Error\r\n\r\n");
375 exit(1);
376 }
377
378 show:
379 now = time(NULL);
380 render(r);
381
382 return 0;
383 }