Add some subscription (a.k.a. "follow") functionality
[blerg.git] / http / http_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 <stdlib.h>
6 #include <string.h>
7 #include <microhttpd.h>
8 #include <yajl/yajl_gen.h>
9 #include "database.h"
10 #include "tags.h"
11 #include "auth.h"
12 #include "canned_responses.h"
13 #include "app.h"
14 #include "config.h"
15
16 yajl_gen_config yajl_c = { 0, 0 };
17
18 struct auth_state {
19         struct MHD_PostProcessor *pp;
20         char username[33];
21         char password[33];
22 };
23
24 struct put_state {
25         struct MHD_PostProcessor *pp;
26         char username[33];
27         char *data;
28         int data_size;
29 };
30
31 struct subscribe_state {
32         struct MHD_PostProcessor *pp;
33         char username[33];
34         char to[33];
35 };
36
37 struct get_state {
38         struct blerg *b;
39         yajl_gen g;
40         unsigned int yoff;
41         uint64_t *entries;
42         uint64_t i;
43         int done;
44 };
45
46 struct tag_state {
47         yajl_gen g;
48         unsigned int yoff;
49         struct tag *results;
50         uint64_t i;
51         int done;
52 };
53
54 ssize_t GET_generate_list(void *cls, uint64_t pos, char *buf, size_t max) {
55         struct get_state *gs = cls;
56         const unsigned char *ybuf;
57         char *data;
58         char number[21];
59         unsigned int len;
60
61         if (gs->yoff > 0) {
62                 yajl_gen_get_buf(gs->g, &ybuf, &len);
63                 size_t bytes_remaining = len - gs->yoff;
64                 if (bytes_remaining > max) {
65                         memcpy(buf, ybuf + gs->yoff, max);
66                         gs->yoff += max;
67                         return max;
68                 } else {
69                         memcpy(buf, ybuf + gs->yoff, bytes_remaining);
70                         gs->yoff = 0;
71                         yajl_gen_clear(gs->g);
72                         return bytes_remaining;
73                 }
74         }
75
76         if (gs->done)
77                 return -1;
78
79         if (pos == 0) { /* Start iterating */
80                 yajl_gen_array_open(gs->g);
81         }
82
83         /* Snarf one record */
84         json_generate_one_record(gs->g, NULL, gs->b, gs->entries[gs->i], 0);
85
86         if (gs->i == 0) {
87                 yajl_gen_array_close(gs->g);
88                 gs->done = 1;
89         }
90         gs->i--;
91
92
93         yajl_gen_get_buf(gs->g, &ybuf, &len);
94         if (len > max) {
95                 memcpy(buf, ybuf, max);
96                 gs->yoff = max;
97                 return max;
98         } else {
99                 memcpy(buf, ybuf, len);
100                 yajl_gen_clear(gs->g);
101                 return len;
102         }
103 }
104
105 void GET_generate_list_free(void *cls) {
106         struct get_state *gs = cls;
107
108         blerg_close(gs->b);
109         yajl_gen_free(gs->g);
110         free(gs->entries);
111         free(gs);
112 }
113
114 ssize_t GET_generate_taglist(void *cls, uint64_t pos, char *buf, size_t max) {
115         struct tag_state *ts = cls;
116         struct blerg *b;
117         const unsigned char *ybuf;
118         unsigned int len;
119
120         if (ts->yoff > 0) {
121                 yajl_gen_get_buf(ts->g, &ybuf, &len);
122                 size_t bytes_remaining = len - ts->yoff;
123                 if (bytes_remaining > max) {
124                         memcpy(buf, ybuf + ts->yoff, max);
125                         ts->yoff += max;
126                         return max;
127                 } else {
128                         memcpy(buf, ybuf + ts->yoff, bytes_remaining);
129                         ts->yoff = 0;
130                         yajl_gen_clear(ts->g);
131                         return bytes_remaining;
132                 }
133         }
134
135         if (ts->done)
136                 return -1;
137
138         if (pos == 0) { /* Start iterating */
139                 yajl_gen_array_open(ts->g);
140         }
141
142         /* Snarf one record */
143         b = blerg_open(ts->results[ts->i].author);
144         if (b != NULL) {
145                 json_generate_one_record(ts->g, ts->results[ts->i].author, b, ts->results[ts->i].record, 0);
146                 blerg_close(b);
147         }
148
149         if (ts->i == 0) {
150                 yajl_gen_array_close(ts->g);
151                 ts->done = 1;
152         }
153
154         ts->i--;
155
156         yajl_gen_get_buf(ts->g, &ybuf, &len);
157         if (len > max) {
158                 memcpy(buf, ybuf, max);
159                 ts->yoff = max;
160                 return max;
161         } else {
162                 memcpy(buf, ybuf, len);
163                 yajl_gen_clear(ts->g);
164                 return len;
165         }
166 }
167
168 void GET_generate_taglist_free(void *cls) {
169         struct tag_state *ts = cls;
170
171         yajl_gen_free(ts->g);
172         free(ts->results);
173         free(ts);
174 }
175
176 int POST_auth_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) {
177         struct auth_state *as = cls;
178
179         if (strncmp(key, "username", 9) == 0) {
180                 if (size > 32) size = 32;
181                 memcpy(as->username, data, size);
182                 as->username[size] = 0;
183         } else if (strncmp(key, "password", 9) == 0) {
184                 if (size > 32) size = 32;
185                 memcpy(as->password, data, size);
186                 as->password[size] = 0;
187         }
188
189         return MHD_YES;
190 }
191
192 int POST_put_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) {
193         struct put_state *ps = cls;
194
195         if (strncmp(key, "data", 5) == 0) {
196                 if (ps->data == NULL) {
197                         ps->data_size = size;
198                         ps->data = malloc(size);
199                 } else {
200                         if (ps->data_size + size > MAX_RECORD_SIZE) {
201                                 size = MAX_RECORD_SIZE - ps->data_size;
202                         }
203                         ps->data_size += size;
204                         ps->data = realloc(ps->data, ps->data_size);
205                 }
206                 memcpy(ps->data + off, data, size);
207                 if (ps->data_size == MAX_RECORD_SIZE)
208                         return MHD_NO;
209         } else if (strncmp(key, "username", 9) == 0) {
210                 if (size > 32) size = 32;
211                 memcpy(ps->username, data, size);
212                 ps->username[size] = 0;
213         }
214
215         return MHD_YES;
216 }
217
218 int POST_subscribe_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) {
219         struct subscribe_state *ss = cls;
220
221         if (strncmp(key, "username", 9) == 0) {
222                 if (size > 32) size = 32;
223                 memcpy(ss->username, data, size);
224                 ss->username[size] = 0;
225         } else if (strncmp(key, "to", 3) == 0) {
226                 if (size > 32) size = 32;
227                 memcpy(ss->to, data, size);
228                 ss->to[size] = 0;
229         }
230
231         return MHD_YES;
232 }
233
234 struct MHD_Response *create_response_for_range(struct blerg *b, uint64_t from, uint64_t to) {
235         struct MHD_Response *response;
236         struct get_state *gs = malloc(sizeof(struct get_state));
237         gs->b = b;
238
239         uint64_t record_count = blerg_get_record_count(b);
240
241         if (from > to || from >= record_count || to >= record_count || to - from > 99) {
242                 blerg_close(b);
243                 free(gs);
244                 return NULL;
245         }
246
247         gs->entries = make_sequential_list(from, to);
248         gs->i = to - from;
249
250         gs->g = yajl_gen_alloc(&yajl_c, NULL);
251         gs->yoff = gs->done = 0;
252
253         response = MHD_create_response_from_callback(-1, 262144, &GET_generate_list, gs, &GET_generate_list_free);
254
255         return response;
256 }
257
258 struct MHD_Response *create_tag_response(struct tag *results, uint64_t len) {
259         struct tag_state *ts = malloc(sizeof(struct tag_state));
260         ts->g = yajl_gen_alloc(&yajl_c, NULL);
261         ts->results = results;
262         ts->i = len - 1;
263         ts->yoff = ts->done = 0;
264
265         return MHD_create_response_from_callback(-1, 262144, &GET_generate_taglist, ts, &GET_generate_taglist_free);
266 }
267
268 static int
269 ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const char *method,
270           const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) {
271         struct MHD_Response *response;
272         int ret, len;
273         struct url_info info;
274         char *data;
275
276         if (strncmp(url, "/get", 4) == 0 && strlen(url) > 4) {
277                 if (*ptr == NULL) {
278                         if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
279                                 return respond_405(connection);
280
281                         *ptr = (void *) 1;
282                         return MHD_YES;
283                 }
284
285                 if (url[4] != '/')
286                         return respond_404(connection);
287
288                 ret = parse_url_info(url + 5, &info);
289                 if ((ret & URL_INFO_NAME) == 0)
290                         return respond_404(connection);
291
292                 if (!blerg_exists(info.name))
293                         return respond_404(connection);
294
295                 *ptr == NULL;
296
297                 struct blerg *b = blerg_open(info.name);
298
299                 if ((ret & URL_INFO_RECORD) && (ret & URL_INFO_RECORD_TO)) {
300                         response = create_response_for_range(b, info.record, info.record_to);
301                 } else if (ret & URL_INFO_RECORD) {
302                         ret = blerg_fetch(b, info.record, &data, &len);
303                         blerg_close(b);
304
305                         if (ret == 0)
306                                 return respond_404(connection);
307                         response = MHD_create_response_from_data(len, data, MHD_YES, MHD_NO);
308                 } else {
309                         uint64_t record_count, from, to;
310                         record_count = blerg_get_record_count(b);
311                         if (record_count == 0) {
312                                 blerg_close(b);
313                                 response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
314                         } else {
315                                 to = record_count - 1;
316                                 from = (record_count > 50 ? to - 49 : 0);
317                                 response = create_response_for_range(b, from, to);
318                         }
319                 }
320
321                 if (response == NULL) {
322                         blerg_close(b);
323                         return respond_JSON_Failure(connection);
324                 }
325
326                 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
327                 MHD_destroy_response(response);
328                 return ret;
329         } else if (strncmp(url, "/tag", 4) == 0 && strlen(url) > 4) {
330                 if (*ptr == NULL) {
331                         if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
332                                 return respond_405(connection);
333
334                         *ptr = (void *) 1;
335                         return MHD_YES;
336                 }
337
338                 if (url[4] != '/')
339                         return respond_404(connection);
340
341                 ret = parse_url_info(url + 5, &info);
342                 if ((ret & URL_INFO_NAME) == 0)
343                         return respond_404(connection);
344
345                 if (!tag_exists(info.name))
346                         return respond_404(connection);
347
348                 int recs = 50;
349                 struct tag *taglist = tag_list(info.name, 0, &recs, -1);
350
351                 if (recs == 0) {
352                         response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
353                 } else {
354                         response = create_tag_response(taglist, recs);
355                 }
356
357                 if (response == NULL)
358                         return respond_JSON_Failure(connection);
359
360                 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
361                 MHD_destroy_response(response);
362
363                 return ret;
364         } else if (strncmp(url, "/put", 4) == 0) {
365                 struct put_state *ps = (struct put_state *) *ptr;
366                 if (*ptr == NULL) {
367                         if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
368                                 return respond_405(connection);
369
370                         if (url[4] == '/')
371                                 return respond_404(connection);
372
373                         *ptr = (void *) 1;
374
375                         struct put_state *ps = malloc(sizeof(struct put_state));
376                         ps->data = NULL;
377                         ps->data_size = 0;
378                         ps->pp = MHD_create_post_processor(connection, 16384, &POST_put_iterator, ps);
379                         ps->username[0] = 0;
380                         *ptr = ps;
381                         return MHD_YES;
382                 }
383
384                 if (*upload_data_size) {
385                         MHD_post_process(ps->pp, upload_data, *upload_data_size);
386                         *upload_data_size = 0;
387                         return MHD_YES;
388                 }
389
390                 if (ps->data == NULL || ps->data_size == 0 || ps->username[0] == 0)
391                         return respond_JSON_Failure(connection);
392
393                 const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
394                 if (!auth_check_token(ps->username, given_token))
395                         return respond_JSON_Failure(connection);
396
397                 struct blerg *b = blerg_open(ps->username);
398                 if (b == NULL)
399                         return respond_JSON_Failure(connection);
400                 ret = blerg_store(b, ps->data, ps->data_size);
401                 blerg_close(b);
402                 if (ret == -1)
403                         return respond_JSON_Failure(connection);
404
405                 MHD_destroy_post_processor(ps->pp);
406                 free(ps->data);
407                 free(ps);
408                 *ptr = NULL;
409
410                 return respond_JSON_Success(connection);
411         } else if (strncmp(url, "/info", 5) == 0) {
412                 if (*ptr == NULL) {
413                         *ptr = (void *) 1;
414
415                         if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
416                                 return respond_405(connection);
417                         return MHD_YES;
418                 }
419
420
421                 if (url[5] != '/')
422                         return respond_404(connection);
423
424                 ret = parse_url_info(url + 6, &info);
425                 if ((ret & URL_INFO_NAME) == 0)
426                         return respond_404(connection);
427
428                 if (!blerg_exists(info.name))
429                         return respond_404(connection);
430
431                 *ptr == NULL;
432
433                 struct blerg *b = blerg_open(info.name);
434                 uint64_t record_count = blerg_get_record_count(b);
435                 blerg_close(b);
436
437                 char number[21];
438                 yajl_gen g = yajl_gen_alloc(&yajl_c, NULL);
439                 yajl_gen_map_open(g);
440                 yajl_gen_string(g, "record_count", 12);
441                 snprintf(number, 21, "%llu", record_count);
442                 yajl_gen_string(g, number, strlen(number));
443                 yajl_gen_map_close(g);
444
445                 const unsigned char *ybuf;
446                 yajl_gen_get_buf(g, &ybuf, &len);
447
448                 response = MHD_create_response_from_data(len, (void *)ybuf, MHD_NO, MHD_YES);
449                 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
450                 MHD_destroy_response(response);
451
452                 yajl_gen_free(g);
453
454                 return ret;
455         } else if (strncmp(url, "/create", 8) == 0) {
456                 struct auth_state *as = (struct auth_state *) *ptr;
457
458                 if (as == NULL) {
459                         if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
460                                 return respond_405(connection);
461
462                         struct auth_state *as = malloc(sizeof(struct auth_state));
463                         as->username[0] = as->password[0] = 0;
464                         as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
465                         *ptr = as;
466                         return MHD_YES;
467                 }
468
469                 if (*upload_data_size) {
470                         MHD_post_process(as->pp, upload_data, *upload_data_size);
471                         *upload_data_size = 0;
472                         return MHD_YES;
473                 }
474
475                 if (as->username[0] == 0 || as->password[0] == 0)
476                         return respond_JSON_Failure(connection);
477
478                 if (blerg_exists(as->username))
479                         return respond_JSON_Failure(connection);
480
481                 struct blerg *b = blerg_open(as->username);
482                 blerg_close(b);
483                 auth_set_password(as->username, as->password);
484
485                 MHD_destroy_post_processor(as->pp);
486                 free(as);
487                 *ptr = NULL;
488
489                 return respond_JSON_Success(connection);
490         } else if (strncmp(url, "/login", 7) == 0) {
491                 struct auth_state *as = (struct auth_state *) *ptr;
492
493                 if (as == NULL) {
494                         if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
495                                 return respond_405(connection);
496
497                         struct auth_state *as = malloc(sizeof(struct auth_state));
498                         as->username[0] = as->password[0] = 0;
499                         as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
500                         *ptr = as;
501                         return MHD_YES;
502                 }
503
504                 if (*upload_data_size) {
505                         MHD_post_process(as->pp, upload_data, *upload_data_size);
506                         *upload_data_size = 0;
507                         return MHD_YES;
508                 }
509
510                 if (as->username[0] == 0 || as->password[0] == 0)
511                         return respond_JSON_Failure(connection);
512
513                 if (!auth_login(as->username, as->password))
514                         return respond_JSON_Failure(connection);
515
516                 response = MHD_create_response_from_data(strlen(JSON_SUCCESS), JSON_SUCCESS, MHD_NO, MHD_NO);
517
518                 char *token = auth_get_token(as->username);
519                 data = malloc(512);
520                 snprintf(data, 512, "auth=%s", token);
521                 MHD_add_response_header(response, "Set-Cookie", data);
522                 free(token);
523                 free(data);
524
525                 MHD_destroy_post_processor(as->pp);
526                 free(as);
527                 *ptr = NULL;
528
529                 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
530                 MHD_destroy_response(response);
531
532                 return ret;
533         } else if (strncmp(url, "/logout", 8) == 0) {
534                 struct auth_state *as = (struct auth_state *) *ptr;
535
536                 if (as == NULL) {
537                         if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
538                                 return respond_405(connection);
539
540                         struct auth_state *as = malloc(sizeof(struct auth_state));
541                         as->username[0] = as->password[0] = 0;
542                         as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
543                         *ptr = as;
544                         return MHD_YES;
545                 }
546
547                 if (*upload_data_size) {
548                         MHD_post_process(as->pp, upload_data, *upload_data_size);
549                         *upload_data_size = 0;
550                         return MHD_YES;
551                 }
552
553                 const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
554                 if (auth_check_token(as->username, given_token)) {
555                         auth_logout(as->username);
556                         return respond_JSON_Success(connection);
557                 } else {
558                         return respond_JSON_Failure(connection);
559                 }
560         } else if (strncmp(url, "/subscribe", 11) == 0) {
561                 struct subscribe_state *ss = (struct subscribe_state *) *ptr;
562
563                 if (ss == NULL) {
564                         if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
565                                 return respond_405(connection);
566
567                         struct subscribe_state *ss = malloc(sizeof(struct subscribe_state));
568                         ss->username[0] = ss->to[0] = 0;
569                         ss->pp = MHD_create_post_processor(connection, 1024, &POST_subscribe_iterator, ss);
570                         *ptr = ss;
571                         return MHD_YES;
572                 }
573
574                 if (*upload_data_size) {
575                         MHD_post_process(ss->pp, upload_data, *upload_data_size);
576                         *upload_data_size = 0;
577                         return MHD_YES;
578                 }
579
580                 const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
581                 if (auth_check_token(ss->username, given_token)) {
582                         subscription_add(ss->username, ss->to);
583                         return respond_JSON_Success(connection);
584                 } else {
585                         return respond_JSON_Failure(connection);
586                 }
587         } else {
588                 return respond_404(connection);
589         }
590 }
591
592
593 int main(int argc, char *argv[]) {
594         struct MHD_Daemon *daemon;
595         fd_set rs, ws, es;
596         int max;
597
598         init_responses();
599
600         daemon = MHD_start_daemon(MHD_USE_DEBUG, HTTP_BLERG_PORT, NULL, NULL, &ahc_derp, NULL, MHD_OPTION_END);
601         if (daemon == NULL) {
602                 fprintf(stderr, "Could not start web server\n");
603                 return 1;
604         }
605
606         while (1) {
607                 FD_ZERO(&rs); FD_ZERO(&ws); FD_ZERO(&es);
608                 if (MHD_get_fdset(daemon, &rs, &ws, &es, &max) != MHD_YES) {
609                         fprintf(stderr, "Fatal error getting fd sets\n");
610                         break;
611                 }
612                 select(max + 1, &rs, &ws, &es, NULL);
613                 MHD_run(daemon);
614         }
615         MHD_stop_daemon(daemon);
616         return 0;
617 }