/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;
}