/http/http_blerg.c
/* 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 <microhttpd.h>
#include <yajl/yajl_gen.h>
#include "database.h"
#include "tags.h"
#include "subscription.h"
#include "auth.h"
#include "canned_responses.h"
#include "app.h"
#include "config.h"
yajl_gen_config yajl_c = { 0, 0 };
struct auth_state {
struct MHD_PostProcessor *pp;
char username[33];
char password[33];
};
struct put_state {
struct MHD_PostProcessor *pp;
char username[33];
char *data;
int data_size;
};
struct get_state {
struct blerg *b;
yajl_gen g;
unsigned int yoff;
uint64_t *entries;
uint64_t i;
int done;
};
struct blergref_state {
yajl_gen g;
unsigned int yoff;
struct blergref *results;
uint64_t i;
int done;
};
ssize_t GET_generate_list(void *cls, uint64_t pos, char *buf, size_t max) {
struct get_state *gs = cls;
const unsigned char *ybuf;
char *data;
char number[21];
unsigned int len;
if (gs->yoff > 0) {
yajl_gen_get_buf(gs->g, &ybuf, &len);
size_t bytes_remaining = len - gs->yoff;
if (bytes_remaining > max) {
memcpy(buf, ybuf + gs->yoff, max);
gs->yoff += max;
return max;
} else {
memcpy(buf, ybuf + gs->yoff, bytes_remaining);
gs->yoff = 0;
yajl_gen_clear(gs->g);
return bytes_remaining;
}
}
if (gs->done)
return -1;
if (pos == 0) { /* Start iterating */
yajl_gen_array_open(gs->g);
}
/* Snarf one record */
json_generate_one_record(gs->g, NULL, gs->b, gs->entries[gs->i], 0);
if (gs->i == 0) {
yajl_gen_array_close(gs->g);
gs->done = 1;
}
gs->i--;
yajl_gen_get_buf(gs->g, &ybuf, &len);
if (len > max) {
memcpy(buf, ybuf, max);
gs->yoff = max;
return max;
} else {
memcpy(buf, ybuf, len);
yajl_gen_clear(gs->g);
return len;
}
}
void GET_generate_list_free(void *cls) {
struct get_state *gs = cls;
blerg_close(gs->b);
yajl_gen_free(gs->g);
free(gs->entries);
free(gs);
}
ssize_t GET_generate_blergref_list(void *cls, uint64_t pos, char *buf, size_t max) {
struct blergref_state *bs = cls;
struct blerg *b;
const unsigned char *ybuf;
unsigned int len;
if (bs->yoff > 0) {
yajl_gen_get_buf(bs->g, &ybuf, &len);
size_t bytes_remaining = len - bs->yoff;
if (bytes_remaining > max) {
memcpy(buf, ybuf + bs->yoff, max);
bs->yoff += max;
return max;
} else {
memcpy(buf, ybuf + bs->yoff, bytes_remaining);
bs->yoff = 0;
yajl_gen_clear(bs->g);
return bytes_remaining;
}
}
if (bs->done)
return -1;
if (pos == 0) { /* Start iterating */
yajl_gen_array_open(bs->g);
}
/* Snarf one record */
b = blerg_open(bs->results[bs->i].author);
if (b != NULL) {
json_generate_one_record(bs->g, bs->results[bs->i].author, b, bs->results[bs->i].record, 0);
blerg_close(b);
}
if (bs->i == 0) {
yajl_gen_array_close(bs->g);
bs->done = 1;
}
bs->i--;
yajl_gen_get_buf(bs->g, &ybuf, &len);
if (len > max) {
memcpy(buf, ybuf, max);
bs->yoff = max;
return max;
} else {
memcpy(buf, ybuf, len);
yajl_gen_clear(bs->g);
return len;
}
}
void GET_generate_blergref_list_free(void *cls) {
struct blergref_state *bs = cls;
yajl_gen_free(bs->g);
free(bs->results);
free(bs);
}
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) {
struct put_state *ps = cls;
if (strncmp(key, "data", 5) == 0) {
if (ps->data == NULL) {
ps->data_size = size;
ps->data = malloc(size);
} else {
if (ps->data_size + size > MAX_RECORD_SIZE) {
size = MAX_RECORD_SIZE - ps->data_size;
}
ps->data_size += size;
ps->data = realloc(ps->data, ps->data_size);
}
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 process_put(struct MHD_Connection *connection, const char *method, const char *upload_data, size_t *upload_data_size, void **ptr) {
struct put_state *ps = (struct put_state *) *ptr;
if (ps == NULL) {
if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
return respond_405(connection);
*ptr = (void *) 1;
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;
}
if (*upload_data_size) {
MHD_post_process(ps->pp, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
return MHD_NO;
}
int process_and_check_put(struct MHD_Connection *connection, const char *method, const char *upload_data, size_t *upload_data_size, void **ptr) {
struct put_state *ps = (struct put_state *) *ptr;
if (process_put(connection, method, upload_data, upload_data_size, ptr) == MHD_YES)
return MHD_YES;
const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
if (!auth_check_token(ps->username, given_token))
return respond_403(connection);
return MHD_NO;
}
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(as->username, data, size);
as->username[size] = 0;
} else if (strncmp(key, "password", 9) == 0) {
if (size > 32) size = 32;
memcpy(as->password, data, size);
as->password[size] = 0;
}
return MHD_YES;
}
int process_auth(struct MHD_Connection *connection, const char *method, const char *upload_data, size_t *upload_data_size, void **ptr) {
struct auth_state *as = (struct auth_state *) *ptr;
if (as == NULL) {
if (strcmp(method, MHD_HTTP_METHOD_POST) != 0)
return respond_405(connection);
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;
}
return MHD_NO;
}
int process_and_check_auth(struct MHD_Connection *connection, const char *method, const char *upload_data, size_t *upload_data_size, void **ptr) {
struct auth_state *as = (struct auth_state *) *ptr;
if (process_auth(connection, method, upload_data, upload_data_size, ptr) == MHD_YES)
return MHD_YES;
const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
if (!auth_check_token(as->username, given_token))
return respond_403(connection);
return MHD_NO;
}
struct MHD_Response *create_response_for_range(struct blerg *b, uint64_t from, uint64_t to) {
struct MHD_Response *response;
struct get_state *gs = malloc(sizeof(struct get_state));
gs->b = b;
uint64_t record_count = blerg_get_record_count(b);
if (from > to || from >= record_count || to >= record_count || to - from > 99) {
free(gs);
return NULL;
}
gs->entries = make_sequential_list(from, to);
gs->i = to - from;
gs->g = yajl_gen_alloc(&yajl_c, NULL);
gs->yoff = gs->done = 0;
response = MHD_create_response_from_callback(-1, 262144, &GET_generate_list, gs, &GET_generate_list_free);
return response;
}
struct MHD_Response *create_blergref_response(struct blergref *results, uint64_t len) {
struct blergref_state *bs = malloc(sizeof(struct blergref_state));
bs->g = yajl_gen_alloc(&yajl_c, NULL);
bs->results = results;
bs->i = len - 1;
bs->yoff = bs->done = 0;
return MHD_create_response_from_callback(-1, 262144, &GET_generate_blergref_list, bs, &GET_generate_blergref_list_free);
}
static int
ahc_derp (void *cls, struct MHD_Connection *connection, const char *url, const char *method,
const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) {
struct MHD_Response *response;
int ret, len;
struct url_info info;
char *data;
if (strncmp(url, "/get", 4) == 0 && strlen(url) > 4) {
if (*ptr == NULL) {
if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
return respond_405(connection);
*ptr = (void *) 1;
return MHD_YES;
}
if (url[4] != '/')
return respond_404(connection);
ret = parse_url_info(url + 5, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
if (!blerg_exists(info.name))
return respond_404(connection);
*ptr == NULL;
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);
} else if (ret & URL_INFO_RECORD) {
ret = blerg_fetch(b, info.record, &data, &len);
blerg_close(b);
if (ret == 0)
return respond_404(connection);
response = MHD_create_response_from_data(len, data, MHD_YES, MHD_NO);
} else {
uint64_t record_count, from, to;
record_count = blerg_get_record_count(b);
if (record_count == 0) {
blerg_close(b);
response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
} else {
to = record_count - 1;
from = (record_count > 50 ? to - 49 : 0);
response = create_response_for_range(b, from, to);
}
}
if (response == NULL) {
blerg_close(b);
return respond_JSON_Failure(connection);
}
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
} else if (strncmp(url, "/tag", 4) == 0 && strlen(url) > 4) {
if (*ptr == NULL) {
if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
return respond_405(connection);
*ptr = (void *) 1;
return MHD_YES;
}
if (url[4] != '/')
return respond_404(connection);
ret = parse_url_info(url + 5, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
if (info.name[0] == 'H')
info.name[0] = '#';
if (!tag_exists(info.name))
return respond_404(connection);
int recs = 50;
struct blergref *taglist = tag_list(info.name, 0, &recs, -1);
if (recs == 0) {
response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
} else {
response = create_blergref_response(taglist, recs);
}
if (response == NULL)
return respond_JSON_Failure(connection);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
} else if (strncmp(url, "/put", 4) == 0) {
if (url[4] == '/')
return respond_404(connection);
ret = process_and_check_put(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct put_state *ps = (struct put_state *) *ptr;
if (ps->data == NULL || ps->data_size == 0)
return respond_JSON_Failure(connection);
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(ps->data);
free(ps);
*ptr = NULL;
return respond_JSON_Success(connection);
} else if (strncmp(url, "/info", 5) == 0) {
if (*ptr == NULL) {
*ptr = (void *) 1;
if (strcmp(method, MHD_HTTP_METHOD_GET) != 0)
return respond_405(connection);
return MHD_YES;
}
if (url[5] != '/')
return respond_404(connection);
ret = parse_url_info(url + 6, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
if (!blerg_exists(info.name))
return respond_404(connection);
*ptr == NULL;
struct blerg *b = blerg_open(info.name);
uint64_t record_count = blerg_get_record_count(b);
blerg_close(b);
char number[21];
yajl_gen g = yajl_gen_alloc(&yajl_c, NULL);
yajl_gen_map_open(g);
yajl_gen_string(g, "record_count", 12);
snprintf(number, 21, "%llu", record_count);
yajl_gen_string(g, number, strlen(number));
yajl_gen_map_close(g);
const unsigned char *ybuf;
yajl_gen_get_buf(g, &ybuf, &len);
response = MHD_create_response_from_data(len, (void *)ybuf, MHD_NO, MHD_YES);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
yajl_gen_free(g);
return ret;
} else if (strncmp(url, "/create", 8) == 0) {
ret = process_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
if (as->username[0] == 0 || as->password[0] == 0)
return respond_JSON_Failure(connection);
if (blerg_exists(as->username))
return respond_JSON_Failure(connection);
struct blerg *b = blerg_open(as->username);
blerg_close(b);
auth_set_password(as->username, as->password);
MHD_destroy_post_processor(as->pp);
free(as);
*ptr = NULL;
return respond_JSON_Success(connection);
} else if (strncmp(url, "/login", 7) == 0) {
ret = process_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
if (as->username[0] == 0 || as->password[0] == 0)
return respond_JSON_Failure(connection);
char *token = auth_login(as->username, as->password);
if (token == NULL)
return respond_JSON_Failure(connection);
response = MHD_create_response_from_data(strlen(JSON_SUCCESS), JSON_SUCCESS, MHD_NO, MHD_NO);
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) {
ret = process_and_check_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
const char *given_token = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "auth");
auth_logout(as->username, given_token);
return respond_JSON_Success(connection);
} else if (strncmp(url, "/subscribe", 10) == 0 || strncmp(url, "/unsubscribe", 12) == 0) {
ret = process_and_check_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
if (url[1] == 'u') {
if (url[12] != '/')
return respond_404(connection);
ret = parse_url_info(url + 13, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
subscription_remove(as->username, info.name);
} else {
if (url[10] != '/')
return respond_404(connection);
ret = parse_url_info(url + 11, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
subscription_add(as->username, info.name);
}
return respond_JSON_Success(connection);
} else if (strncmp(url, "/feed", 6) == 0) {
ret = process_and_check_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
int recs = 50;
struct blergref *feedlist = subscription_list(as->username, 0, &recs, -1);
if (recs == 0) {
response = MHD_create_response_from_data(2, "[]", MHD_NO, MHD_NO);
} else {
response = create_blergref_response(feedlist, recs);
}
if (response == NULL)
return respond_JSON_Failure(connection);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
} else if (strncmp(url, "/feedinfo", 9) == 0) {
ret = process_and_check_auth(connection, method, upload_data, upload_data_size, ptr);
if (ret == MHD_YES)
return MHD_YES;
struct auth_state *as = (struct auth_state *) *ptr;
if (url[9] != '/')
return respond_404(connection);
ret = parse_url_info(url + 10, &info);
if ((ret & URL_INFO_NAME) == 0)
return respond_404(connection);
yajl_gen g = yajl_gen_alloc(&yajl_c, NULL);
yajl_gen_map_open(g);
yajl_gen_string(g, "subscribed", 10);
yajl_gen_bool(g, is_subscribed(as->username, info.name));
yajl_gen_map_close(g);
const unsigned char *ybuf;
yajl_gen_get_buf(g, &ybuf, &len);
response = MHD_create_response_from_data(len, (void *)ybuf, MHD_NO, MHD_YES);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
yajl_gen_free(g);
free(as);
return ret;
} else {
return respond_404(connection);
}
}
int main(int argc, char *argv[]) {
struct MHD_Daemon *daemon;
fd_set rs, ws, es;
int max;
init_responses();
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;
}
while (1) {
FD_ZERO(&rs); FD_ZERO(&ws); FD_ZERO(&es);
if (MHD_get_fdset(daemon, &rs, &ws, &es, &max) != MHD_YES) {
fprintf(stderr, "Fatal error getting fd sets\n");
break;
}
select(max + 1, &rs, &ws, &es, NULL);
MHD_run(daemon);
}
MHD_stop_daemon(daemon);
return 0;
}