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