Make /status work with GET for non-changing requests
[blerg.git] / cgi / cgi_blerg.c
1 /* Blerg is (C) 2011 The Dominion of Awesome, and is distributed under a
2  * BSD-style license.  Please see the COPYING file for details.
3  */
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <cgi-util.h>
8 #include <yajl/yajl_gen.h>
9 #include "database.h"
10 #include "tags.h"
11 #include "auth.h"
12 #include "subscription.h"
13 #include "json.h"
14 #include "canned_responses.h"
15 #include "app.h"
16 #include "config.h"
17
18 yajl_gen_config yajl_c = { 0, 0 };
19
20 int check_auth(struct auth_cookie *ac) {
21         const char *given_cookie = cgi_getcookie("auth");
22
23         if (parse_auth_cookie(given_cookie, ac) != 1) {
24                 respond_403();
25                 return 0;
26         }
27
28         if (!auth_check_token(ac->name, ac->token)) {
29                 respond_403();
30                 return 0;
31         }
32         return 1;
33 }
34
35 void respond_yajl(yajl_gen g) {
36         const unsigned char *ybuf;
37         unsigned int content_len;
38
39         yajl_gen_get_buf(g, &ybuf, &content_len);
40
41         printf("Content-type: application/json\r\n");
42         printf("Content-length: %d\r\n\r\n", content_len);
43         fwrite(ybuf, content_len, 1, stdout);
44 }
45
46 void respond_for_range(struct blerg *b, uint64_t from, uint64_t to) {
47         const unsigned char *ybuf;
48         unsigned int len;
49         uint64_t i;
50         yajl_gen g;
51         uint64_t record_count = blerg_get_record_count(b);
52
53         printf("Content-type: application/json\r\n\r\n");
54
55         if (from > to || from >= record_count || to >= record_count || to - from > 99) {
56                 respond_JSON_Failure();
57                 return;
58         }
59
60         g = yajl_gen_alloc(&yajl_c, NULL);
61         yajl_gen_array_open(g);
62
63         for (i = to; i != from - 1; i--) {
64                 json_generate_one_record(g, NULL, b, i, 0);
65                 yajl_gen_get_buf(g, &ybuf, &len);
66                 fwrite(ybuf, len, 1, stdout);
67                 yajl_gen_clear(g);
68         }
69
70         yajl_gen_array_close(g);
71         yajl_gen_get_buf(g, &ybuf, &len);
72         fwrite(ybuf, len, 1, stdout);
73         yajl_gen_free(g);
74 }
75
76 void respond_blergref_list(struct blergref * results, int i) {
77         const unsigned char *ybuf;
78         unsigned int len;
79         struct blerg *b;
80         yajl_gen g;
81
82         i--;
83
84         printf("Content-type: application/json\r\n\r\n");
85         g = yajl_gen_alloc(&yajl_c, NULL);
86
87         yajl_gen_array_open(g);
88
89         while (i >= 0) {
90                 b = blerg_open(results[i].author);
91                 if (b != NULL) {
92                         json_generate_one_record(g, results[i].author, b, results[i].record, 0);
93                         blerg_close(b);
94                 }
95                 yajl_gen_get_buf(g, &ybuf, &len);
96                 fwrite(ybuf, len, 1, stdout);
97                 yajl_gen_clear(g);
98
99                 i--;
100         }
101
102         yajl_gen_array_close(g);
103         yajl_gen_get_buf(g, &ybuf, &len);
104         fwrite(ybuf, len, 1, stdout);
105         yajl_gen_free(g);
106 }
107
108 int main(int argc, char *argv[]) {
109         char *path;
110         char *request_method;
111         int ret, len;
112         struct url_info info;
113         struct auth_cookie ac;
114         char *data;
115
116         if (!blerg_init())
117                 exit(1);
118
119         if (cgi_init() != CGIERR_NONE)
120                 exit(0);
121
122         path = getenv("PATH_INFO");
123         if (path == NULL) {
124                 respond_404();
125                 exit(0);
126         }
127         request_method = getenv("REQUEST_METHOD");
128         if (request_method == NULL) {
129                 fprintf(stderr, "Request method is null!?\n");
130                 exit(0);
131         }
132
133         if (strncmp(path, "/get", 4) == 0 && strlen(path) > 4) {
134                 if (strncmp(request_method, "GET", 4) != 0) {
135                         respond_405();
136                         exit(0);
137                 }
138
139                 if (path[4] != '/') {
140                         respond_404();
141                         exit(0);
142                 }
143
144                 ret = parse_url_info(path + 5, &info);
145                 if ((ret & URL_INFO_NAME) == 0) {
146                         respond_404();
147                         exit(0);
148                 }
149
150                 if (!blerg_exists(info.name)) {
151                         respond_404();
152                         exit(0);
153                 }
154
155                 struct blerg *b = blerg_open(info.name);
156
157                 if ((ret & URL_INFO_RECORD) && (ret & URL_INFO_RECORD_TO)) {
158                         respond_for_range(b, info.record, info.record_to);
159                 } else if (ret & URL_INFO_RECORD) {
160                         ret = blerg_fetch(b, info.record, &data, &len);
161
162                         if (ret == 0) {
163                                 respond_404();
164                                 exit(0);
165                         }
166                         respond_simple_data(data, len);
167                 } else {
168                         uint64_t record_count, from, to;
169                         record_count = blerg_get_record_count(b);
170                         if (record_count == 0) {
171                                 respond_simple_data("[]", 2);
172                         } else {
173                                 to = record_count - 1;
174                                 from = (record_count > 50 ? to - 49 : 0);
175                                 respond_for_range(b, from, to);
176                         }
177                 }
178
179                 blerg_close(b);
180         } else if (strncmp(path, "/tag", 4) == 0 && strlen(path) > 4) {
181                 if (strcmp(request_method, "GET") != 0) {
182                         respond_405();
183                         exit(0);
184                 }
185
186                 if (path[4] != '/') {
187                         respond_404();
188                         exit(0);
189                 }
190
191                 ret = parse_url_info(path + 5, &info);
192                 if ((ret & URL_INFO_NAME) == 0) {
193                         respond_404();
194                         exit(0);
195                 }
196
197                 if (info.name[0] == 'H')
198                         info.name[0] = '#';
199                 if (!tag_exists(info.name)) {
200                         respond_404();
201                         exit(0);
202                 }
203
204                 int recs = 50;
205                 struct blergref *taglist = tag_list(info.name, 0, &recs, -1);
206
207                 if (recs == 0) {
208                         respond_simple_data("[]", 2);
209                 } else {
210                         respond_blergref_list(taglist, recs);
211                 }
212         } else if (strncmp(path, "/put", 4) == 0) {
213                 if (strcmp(request_method, "POST") != 0) {
214                         respond_405();
215                         exit(0);
216                 }
217
218                 if (!check_auth(&ac))
219                         exit(0);
220
221                 if (path[4] == '/') {
222                         respond_404();
223                         exit(0);
224                 }
225
226                 const char *data = cgi_getentrystr("data");
227                 if (data == NULL || data[0] == 0) {
228                         respond_JSON_Failure();
229                         exit(0);
230                 }
231
232                 struct blerg *b = blerg_open(ac.name);
233                 if (b == NULL) {
234                         respond_JSON_Failure();
235                         exit(0);
236                 }
237                 ret = blerg_store(b, data, strlen(data));
238                 blerg_close(b);
239                 if (ret == -1) {
240                         respond_JSON_Failure();
241                         exit(0);
242                 }
243
244                 respond_JSON_Success();
245         } else if (strncmp(path, "/info", 5) == 0) {
246                 if (strcmp(request_method, "GET") != 0) {
247                         respond_405();
248                         exit(0);
249                 }
250
251                 if (path[5] != '/') {
252                         respond_404();
253                         exit(0);
254                 }
255
256                 ret = parse_url_info(path + 6, &info);
257                 if ((ret & URL_INFO_NAME) == 0) {
258                         respond_404();
259                         exit(0);
260                 }
261
262                 if (!blerg_exists(info.name)) {
263                         respond_404();
264                         exit(0);
265                 }
266
267                 struct blerg *b = blerg_open(info.name);
268                 uint64_t record_count = blerg_get_record_count(b);
269                 blerg_close(b);
270
271                 char number[21];
272                 yajl_gen g = yajl_gen_alloc(&yajl_c, NULL);
273                 yajl_gen_map_open(g);
274                 yajl_gen_string(g, (unsigned char *)"record_count", 12);
275                 snprintf(number, 21, "%llu", record_count);
276                 yajl_gen_string(g, (unsigned char *)number, strlen(number));
277                 yajl_gen_map_close(g);
278
279                 respond_yajl(g);
280
281                 yajl_gen_free(g);
282         } else if (strncmp(path, "/create", 8) == 0) {
283                 if (strcmp(request_method, "POST") != 0) {
284                         respond_405();
285                         exit(0);
286                 }
287
288                 const char *username = cgi_getentrystr("username");
289                 const char *password = cgi_getentrystr("password");
290                 if (username == NULL || username[0] == 0 ||
291                     password == NULL || password[0] == 0) {
292                         respond_JSON_Failure();
293                         exit(0);
294                 }
295
296                 if (blerg_exists(username)) {
297                         respond_JSON_Failure();
298                         exit(0);
299                 }
300
301                 struct blerg *b = blerg_open(username);
302                 if (b != NULL) {
303                         blerg_close(b);
304                         auth_set_password(username, password);
305                         
306                         respond_JSON_Success();
307                 } else {
308                         respond_JSON_Failure();
309                 }
310         } else if (strncmp(path, "/login", 7) == 0) {
311                 if (strcmp(request_method, "POST") != 0) {
312                         respond_405();
313                         exit(0);
314                 }
315
316                 const char *username = cgi_getentrystr("username");
317                 const char *password = cgi_getentrystr("password");
318                 if (username == NULL || username[0] == 0 ||
319                     password == NULL || password[0] == 0) {
320                         respond_JSON_Failure();
321                         exit(0);
322                 }
323
324                 char *token = auth_login(username, password);
325                 if (token == NULL) {
326                         respond_JSON_Failure();
327                         exit(0);
328                 }
329
330                 printf("Set-Cookie: auth=%s/%s\r\n", username, token);
331                 free(token);
332
333                 respond_JSON_Success();
334         } else if (strncmp(path, "/logout", 8) == 0) {
335                 if (strcmp(request_method, "POST") != 0) {
336                         respond_405();
337                         exit(0);
338                 }
339
340                 if (!check_auth(&ac))
341                         exit(0);
342
343                 auth_logout(ac.name, ac.token);
344                 respond_JSON_Success();
345         } else if (strncmp(path, "/subscribe", 10) == 0 || strncmp(path, "/unsubscribe", 12) == 0) {
346                 if (!check_auth(&ac))
347                         exit(0);
348
349                 if (path[1] == 'u') {
350                         if (path[12] != '/') {
351                                 respond_404();
352                                 exit(0);
353                         }
354
355                         ret = parse_url_info(path + 13, &info);
356                         if ((ret & URL_INFO_NAME) == 0) {
357                                 respond_404();
358                                 exit(0);
359                         }
360
361                         subscription_remove(ac.name, info.name);
362                 } else {
363                         if (path[10] != '/') {
364                                 respond_404();
365                                 exit(0);
366                         }
367
368                         ret = parse_url_info(path + 11, &info);
369                         if ((ret & URL_INFO_NAME) == 0) {
370                                 respond_404();
371                                 exit(0);
372                         }
373
374                         subscription_add(ac.name, info.name);
375                 }
376                 respond_JSON_Success();
377         } else if (strncmp(path, "/feed", 6) == 0) {
378                 if (!check_auth(&ac))
379                         exit(0);
380
381                 int recs = 50;
382                 struct blergref *feedlist = subscription_list(ac.name, 0, &recs, -1);
383
384                 if (recs == 0) {
385                         respond_simple_data("[]", 2);
386                 } else {
387                         respond_blergref_list(feedlist, recs);
388                 }
389         } else if (strncmp(path, "/status", 7) == 0) {
390                 if (!check_auth(&ac))
391                         exit(0);
392
393                 if (strncmp(request_method, "POST", 4) == 0) {
394                         const char *clear = cgi_getentrystr("clear");
395
396                         if (clear != NULL) {
397                                 struct blerg *b = blerg_open(ac.name);
398                                 if (strncmp(clear, "feed", 4) == 0) {
399                                         blerg_set_subscription_mark(b);
400                                 } else if (strncmp(clear, "mentioned", 9) == 0) {
401                                         blerg_set_status(b, BLERGSTATUS_MENTIONED, 0);
402                                 }
403                                 blerg_close(b);
404                                 respond_JSON_Success();
405                         }
406                 } else if (strncmp(request_method, "GET", 3) == 0) {
407                         yajl_gen g;
408
409                         if (path[7] == 0) {  /* No username */
410                                 g = yajl_gen_alloc(&yajl_c, NULL);
411                                 yajl_gen_map_open(g);
412
413                                 struct blerg *b = blerg_open(ac.name);
414                                 uint64_t subscription_mark = blerg_get_subscription_mark(b);
415                                 int mentioned = blerg_get_status(b, BLERGSTATUS_MENTIONED);
416                                 blerg_close(b);
417
418                                 yajl_gen_string(g, (unsigned char *)"feed_new", 8);
419                                 yajl_gen_integer(g, subscription_count_items(ac.name) - subscription_mark);
420
421                                 yajl_gen_string(g, (unsigned char *)"mentioned", 9);
422                                 yajl_gen_bool(g, mentioned);
423
424                                 yajl_gen_map_close(g);
425                                 respond_yajl(g);
426                                 yajl_gen_free(g);
427                         } else {  /* with username */
428                                 g = yajl_gen_alloc(&yajl_c, NULL);
429                                 yajl_gen_map_open(g);
430
431                                 yajl_gen_string(g, (unsigned char *)"subscribed", 10);
432                                 ret = parse_url_info(path + 8, &info);
433                                 if ((ret & URL_INFO_NAME) == 1) {
434                                         yajl_gen_bool(g, is_subscribed(ac.name, info.name));
435                                 } else {
436                                         yajl_gen_bool(g, 0);
437                                 }
438
439                                 yajl_gen_map_close(g);
440                                 respond_yajl(g);
441                                 yajl_gen_free(g);
442                         }
443                 } else {
444                         respond_405();
445                         exit(0);
446                 }
447         } else if (strncmp(path, "/passwd", 7) == 0) {
448                 if (!check_auth(&ac))
449                         exit(0);
450
451                 const char *password = cgi_getentrystr("password");
452                 const char *new_password = cgi_getentrystr("new_password");
453                 if (password == NULL || new_password == NULL) {
454                         respond_JSON_Failure();
455                 } else {
456                         if (auth_check_password(ac.name, password)) {
457                                 auth_set_password(ac.name, new_password);
458                                 respond_JSON_Success();
459                         } else {
460                                 respond_JSON_Failure();
461                         }
462                 }
463         } else {
464                 respond_404();
465                 exit(0);
466         }
467
468         cgi_quit();
469
470         return 0;
471 }