Add some subscription (a.k.a. "follow") functionality
[blerg.git] / http / http_blerg.c
index 73f88fe..a6d3a5f 100644 (file)
@@ -1,3 +1,6 @@
+/* Blerg is (C) 2011 The Dominion of Awesome, and is distributed under a
+ * BSD-style license.  Please see the COPYING file for details.
+ */
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "tags.h"
 #include "auth.h"
 #include "canned_responses.h"
-#include "http.h"
+#include "app.h"
 #include "config.h"
 
-#define URL_INFO_AUTHOR    0x1
-#define URL_INFO_RECORD    0x2
-#define URL_INFO_RECORD_TO 0x4
-#define DERP "DERP DERP DERP"
-
 yajl_gen_config yajl_c = { 0, 0 };
 
-struct create_state {
+struct auth_state {
        struct MHD_PostProcessor *pp;
        char username[33];
        char password[33];
@@ -25,10 +23,17 @@ struct create_state {
 
 struct put_state {
        struct MHD_PostProcessor *pp;
+       char username[33];
        char *data;
        int data_size;
 };
 
+struct subscribe_state {
+       struct MHD_PostProcessor *pp;
+       char username[33];
+       char to[33];
+};
+
 struct get_state {
        struct blerg *b;
        yajl_gen g;
@@ -46,78 +51,6 @@ struct tag_state {
        int done;
 };
 
-int parse_url_info(const char *url, struct url_info *info) {
-       const char *c;
-       int len;
-       info->contents = 0;
-
-       c = strchr(url, '/');
-       if (c == NULL) {
-               len = strlen(url);
-       } else {
-               len = c - url;
-       }
-       if (len == 0)
-               return 0;
-       memcpy(info->author, url, len);
-       info->author[len] = 0;
-       info->contents |= URL_INFO_AUTHOR;
-
-       if (c == NULL || c[1] == 0)
-               return info->contents;
-
-       info->record = strtoull(c + 1, NULL, 10);
-       info->contents |= URL_INFO_RECORD;
-
-       c = strchr(c, '-');
-       if (c == NULL || c[1] == 0)
-               return info->contents;
-
-       info->record_to = strtoull(c + 1, NULL, 10);
-       info->contents |= URL_INFO_RECORD_TO;
-
-       return info->contents;
-}
-
-uint64_t *make_sequential_list(uint64_t from, uint64_t to) {
-       uint64_t len = to - from + 1;
-       uint64_t *list = malloc(len * sizeof(uint64_t));
-       uint64_t i;
-
-       for (i = 0; i < len; i++) {
-               list[i] = from + i;
-       }
-
-       return list;
-}
-
-void json_generate_one_record(yajl_gen g, const char *author, struct blerg *b, uint64_t record) {
-       char *data;
-       char number[21];
-       int len;
-
-       if (!blerg_fetch(b, record, &data, &len)) {
-               fprintf(stderr, "Could not fetch record\n");
-               return;
-       }
-
-       yajl_gen_map_open(g);
-       if (author != NULL) {
-               yajl_gen_string(g, "author", 6);
-               yajl_gen_string(g, author, strlen(author));
-       }
-       yajl_gen_string(g, "record", 6);
-       snprintf(number, 21, "%llu", record);
-       yajl_gen_string(g, number, strlen(number));
-       yajl_gen_string(g, "timestamp", 9);
-       yajl_gen_integer(g, blerg_get_timestamp(b, record));
-       yajl_gen_string(g, "data", 4);
-       yajl_gen_string(g, data, len);
-       yajl_gen_map_close(g);
-
-       free(data);
-}
-
 ssize_t GET_generate_list(void *cls, uint64_t pos, char *buf, size_t max) {
        struct get_state *gs = cls;
        const unsigned char *ybuf;
@@ -148,7 +81,7 @@ ssize_t GET_generate_list(void *cls, uint64_t pos, char *buf, size_t max) {
        }
 
        /* Snarf one record */
-       json_generate_one_record(gs->g, NULL, gs->b, gs->entries[gs->i]);
+       json_generate_one_record(gs->g, NULL, gs->b, gs->entries[gs->i], 0);
 
        if (gs->i == 0) {
                yajl_gen_array_close(gs->g);
@@ -208,14 +141,11 @@ ssize_t GET_generate_taglist(void *cls, uint64_t pos, char *buf, size_t max) {
 
        /* Snarf one record */
        b = blerg_open(ts->results[ts->i].author);
-       if (b == NULL)
-               goto skip_bad_blerg;
-
-       json_generate_one_record(ts->g, ts->results[ts->i].author, b, ts->results[ts->i].record);
-
-       blerg_close(b);
+       if (b != NULL) {
+               json_generate_one_record(ts->g, ts->results[ts->i].author, b, ts->results[ts->i].record, 0);
+               blerg_close(b);
+       }
 
-skip_bad_blerg:
        if (ts->i == 0) {
                yajl_gen_array_close(ts->g);
                ts->done = 1;
@@ -243,17 +173,17 @@ void GET_generate_taglist_free(void *cls) {
        free(ts);
 }
 
-int POST_create_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) {
-       struct create_state *cs = cls;
+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) {
+       struct auth_state *as = cls;
 
        if (strncmp(key, "username", 9) == 0) {
                if (size > 32) size = 32;
-               memcpy(cs->username, data, size);
-               cs->username[size] = 0;
+               memcpy(as->username, data, size);
+               as->username[size] = 0;
        } else if (strncmp(key, "password", 9) == 0) {
                if (size > 32) size = 32;
-               memcpy(cs->password, data, size);
-               cs->password[size] = 0;
+               memcpy(as->password, data, size);
+               as->password[size] = 0;
        }
 
        return MHD_YES;
@@ -276,6 +206,26 @@ int POST_put_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const
                memcpy(ps->data + off, data, size);
                if (ps->data_size == MAX_RECORD_SIZE)
                        return MHD_NO;
+       } else if (strncmp(key, "username", 9) == 0) {
+               if (size > 32) size = 32;
+               memcpy(ps->username, data, size);
+               ps->username[size] = 0;
+       }
+
+       return MHD_YES;
+}
+
+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) {
+       struct subscribe_state *ss = cls;
+
+       if (strncmp(key, "username", 9) == 0) {
+               if (size > 32) size = 32;
+               memcpy(ss->username, data, size);
+               ss->username[size] = 0;
+       } else if (strncmp(key, "to", 3) == 0) {
+               if (size > 32) size = 32;
+               memcpy(ss->to, data, size);
+               ss->to[size] = 0;
        }
 
        return MHD_YES;
@@ -336,15 +286,15 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
                        return respond_404(connection);
 
                ret = parse_url_info(url + 5, &info);
-               if ((ret & URL_INFO_AUTHOR) == 0)
+               if ((ret & URL_INFO_NAME) == 0)
                        return respond_404(connection);
 
-               if (!blerg_exists(info.author))
+               if (!blerg_exists(info.name))
                        return respond_404(connection);
 
                *ptr == NULL;
 
-               struct blerg *b = blerg_open(info.author);
+               struct blerg *b = blerg_open(info.name);
 
                if ((ret & URL_INFO_RECORD) && (ret & URL_INFO_RECORD_TO)) {
                        response = create_response_for_range(b, info.record, info.record_to);
@@ -389,14 +339,14 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
                        return respond_404(connection);
 
                ret = parse_url_info(url + 5, &info);
-               if ((ret & URL_INFO_AUTHOR) == 0)
+               if ((ret & URL_INFO_NAME) == 0)
                        return respond_404(connection);
 
-               if (!tag_exists(info.author))
+               if (!tag_exists(info.name))
                        return respond_404(connection);
 
-               uint64_t recs = 50;
-               struct tag *taglist = tag_list(info.author, 0, &recs, -1);
+               int recs = 50;
+               struct tag *taglist = tag_list(info.name, 0, &recs, -1);
 
                if (recs == 0) {
                        response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
@@ -413,9 +363,6 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
                return ret;
        } else if (strncmp(url, "/put", 4) == 0) {
                struct put_state *ps = (struct put_state *) *ptr;
-               char *username;
-               char password[33];
-
                if (*ptr == NULL) {
                        if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
                                return respond_405(connection);
@@ -425,21 +372,11 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
 
                        *ptr = (void *) 1;
 
-                       username = MHD_digest_auth_get_username(connection);
-                       if (username == NULL)
-                               return respond_401(connection, MHD_NO);
-                       auth_get_password(username, password);
-
-                       ret = MHD_digest_auth_check(connection, REALM, username, password, 300);
-                       free(username);
-
-                       if (ret == MHD_INVALID_NONCE || ret == MHD_NO)
-                               return respond_401(connection, (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO);
-
                        struct put_state *ps = malloc(sizeof(struct put_state));
                        ps->data = NULL;
                        ps->data_size = 0;
                        ps->pp = MHD_create_post_processor(connection, 16384, &POST_put_iterator, ps);
+                       ps->username[0] = 0;
                        *ptr = ps;
                        return MHD_YES;
                }
@@ -450,21 +387,22 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
                        return MHD_YES;
                }
 
-               if (ps->data == NULL || ps->data_size == 0)
+               if (ps->data == NULL || ps->data_size == 0 || ps->username[0] == 0)
                        return respond_JSON_Failure(connection);
 
-               username = MHD_digest_auth_get_username(connection);
-               struct blerg *b = blerg_open(username);
-               if (b == NULL)
+               const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
+               if (!auth_check_token(ps->username, given_token))
                        return respond_JSON_Failure(connection);
-               if (blerg_store(b, ps->data, ps->data_size) == -1) {
-                       blerg_close(b);
+
+               struct blerg *b = blerg_open(ps->username);
+               if (b == NULL)
                        return respond_JSON_Failure(connection);
-               }
+               ret = blerg_store(b, ps->data, ps->data_size);
                blerg_close(b);
+               if (ret == -1)
+                       return respond_JSON_Failure(connection);
 
                MHD_destroy_post_processor(ps->pp);
-               free(username);
                free(ps->data);
                free(ps);
                *ptr = NULL;
@@ -484,15 +422,15 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
                        return respond_404(connection);
 
                ret = parse_url_info(url + 6, &info);
-               if ((ret & URL_INFO_AUTHOR) == 0)
+               if ((ret & URL_INFO_NAME) == 0)
                        return respond_404(connection);
 
-               if (!blerg_exists(info.author))
+               if (!blerg_exists(info.name))
                        return respond_404(connection);
 
                *ptr == NULL;
 
-               struct blerg *b = blerg_open(info.author);
+               struct blerg *b = blerg_open(info.name);
                uint64_t record_count = blerg_get_record_count(b);
                blerg_close(b);
 
@@ -515,40 +453,137 @@ ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const c
 
                return ret;
        } else if (strncmp(url, "/create", 8) == 0) {
-               struct create_state *cs = (struct create_state *) *ptr;
+               struct auth_state *as = (struct auth_state *) *ptr;
 
-               if (cs == NULL) {
+               if (as == NULL) {
                        if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
                                return respond_405(connection);
 
-                       struct create_state *cs = malloc(sizeof(struct create_state));
-                       cs->username[0] = cs->password[0] = 0;
-                       cs->pp = MHD_create_post_processor(connection, 1024, &POST_create_iterator, cs);
-                       *ptr = cs;
+                       struct auth_state *as = malloc(sizeof(struct auth_state));
+                       as->username[0] = as->password[0] = 0;
+                       as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
+                       *ptr = as;
                        return MHD_YES;
                }
 
                if (*upload_data_size) {
-                       MHD_post_process(cs->pp, upload_data, *upload_data_size);
+                       MHD_post_process(as->pp, upload_data, *upload_data_size);
                        *upload_data_size = 0;
                        return MHD_YES;
                }
 
-               if (cs->username[0] == 0 || cs->password[0] == 0)
+               if (as->username[0] == 0 || as->password[0] == 0)
                        return respond_JSON_Failure(connection);
 
-               if (blerg_exists(cs->username))
+               if (blerg_exists(as->username))
                        return respond_JSON_Failure(connection);
 
-               struct blerg *b = blerg_open(cs->username);
+               struct blerg *b = blerg_open(as->username);
                blerg_close(b);
-               auth_set_password(cs->username, cs->password);
+               auth_set_password(as->username, as->password);
 
-               MHD_destroy_post_processor(cs->pp);
-               free(cs);
+               MHD_destroy_post_processor(as->pp);
+               free(as);
                *ptr = NULL;
 
                return respond_JSON_Success(connection);
+       } else if (strncmp(url, "/login", 7) == 0) {
+               struct auth_state *as = (struct auth_state *) *ptr;
+
+               if (as == NULL) {
+                       if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
+                               return respond_405(connection);
+
+                       struct auth_state *as = malloc(sizeof(struct auth_state));
+                       as->username[0] = as->password[0] = 0;
+                       as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
+                       *ptr = as;
+                       return MHD_YES;
+               }
+
+               if (*upload_data_size) {
+                       MHD_post_process(as->pp, upload_data, *upload_data_size);
+                       *upload_data_size = 0;
+                       return MHD_YES;
+               }
+
+               if (as->username[0] == 0 || as->password[0] == 0)
+                       return respond_JSON_Failure(connection);
+
+               if (!auth_login(as->username, as->password))
+                       return respond_JSON_Failure(connection);
+
+               response = MHD_create_response_from_data(strlen(JSON_SUCCESS), JSON_SUCCESS, MHD_NO, MHD_NO);
+
+               char *token = auth_get_token(as->username);
+               data = malloc(512);
+               snprintf(data, 512, "auth=%s", token);
+               MHD_add_response_header(response, "Set-Cookie", data);
+               free(token);
+               free(data);
+
+               MHD_destroy_post_processor(as->pp);
+               free(as);
+               *ptr = NULL;
+
+               ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+               MHD_destroy_response(response);
+
+               return ret;
+       } else if (strncmp(url, "/logout", 8) == 0) {
+               struct auth_state *as = (struct auth_state *) *ptr;
+
+               if (as == NULL) {
+                       if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
+                               return respond_405(connection);
+
+                       struct auth_state *as = malloc(sizeof(struct auth_state));
+                       as->username[0] = as->password[0] = 0;
+                       as->pp = MHD_create_post_processor(connection, 1024, &POST_auth_iterator, as);
+                       *ptr = as;
+                       return MHD_YES;
+               }
+
+               if (*upload_data_size) {
+                       MHD_post_process(as->pp, upload_data, *upload_data_size);
+                       *upload_data_size = 0;
+                       return MHD_YES;
+               }
+
+               const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
+               if (auth_check_token(as->username, given_token)) {
+                       auth_logout(as->username);
+                       return respond_JSON_Success(connection);
+               } else {
+                       return respond_JSON_Failure(connection);
+               }
+       } else if (strncmp(url, "/subscribe", 11) == 0) {
+               struct subscribe_state *ss = (struct subscribe_state *) *ptr;
+
+               if (ss == NULL) {
+                       if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
+                               return respond_405(connection);
+
+                       struct subscribe_state *ss = malloc(sizeof(struct subscribe_state));
+                       ss->username[0] = ss->to[0] = 0;
+                       ss->pp = MHD_create_post_processor(connection, 1024, &POST_subscribe_iterator, ss);
+                       *ptr = ss;
+                       return MHD_YES;
+               }
+
+               if (*upload_data_size) {
+                       MHD_post_process(ss->pp, upload_data, *upload_data_size);
+                       *upload_data_size = 0;
+                       return MHD_YES;
+               }
+
+               const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
+               if (auth_check_token(ss->username, given_token)) {
+                       subscription_add(ss->username, ss->to);
+                       return respond_JSON_Success(connection);
+               } else {
+                       return respond_JSON_Failure(connection);
+               }
        } else {
                return respond_404(connection);
        }
@@ -562,7 +597,7 @@ int main(int argc, char *argv[]) {
 
        init_responses();
 
-       daemon = MHD_start_daemon(MHD_USE_DEBUG, 8080, NULL, NULL, &ahc_derp, NULL, MHD_OPTION_END);
+       daemon = MHD_start_daemon(MHD_USE_DEBUG, HTTP_BLERG_PORT, NULL, NULL, &ahc_derp, NULL, MHD_OPTION_END);
        if (daemon == NULL) {
                fprintf(stderr, "Could not start web server\n");
                return 1;