/cgi/cgi_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 <string.h>
#include <stdlib.h>
#include <cgi-util.h>
#include <yajl/yajl_gen.h>
#include "database.h"
#include "tags.h"
#include "auth.h"
#include "subscription.h"
#include "json.h"
#include "canned_responses.h"
#include "app.h"
#include "config.h"

yajl_gen_config yajl_c = { 0, 0 };

int check_auth(struct auth_cookie *ac) {
	const char *given_cookie = cgi_getcookie("auth");

	if (parse_auth_cookie(given_cookie, ac) != 1) {
		respond_403();
		return 0;
	}

	if (!auth_check_token(ac->name, ac->token)) {
		respond_403();
		return 0;
	}
	return 1;
}

void respond_yajl(yajl_gen g) {
	const unsigned char *ybuf;
	unsigned int content_len;

	yajl_gen_get_buf(g, &ybuf, &content_len);

	printf("Content-type: application/json\r\n");
	printf("Content-length: %d\r\n\r\n", content_len);
	fwrite(ybuf, content_len, 1, stdout);
}

void respond_for_range(struct blerg *b, uint64_t from, uint64_t to) {
	const unsigned char *ybuf;
	unsigned int len;
	uint64_t i;
	yajl_gen g;
	uint64_t record_count = blerg_get_record_count(b);

	printf("Content-type: application/json\r\n\r\n");

	if (from > to || from >= record_count || to >= record_count || to - from > 99) {
		respond_JSON_Failure();
		return;
	}

	g = yajl_gen_alloc(&yajl_c, NULL);
	yajl_gen_array_open(g);

	for (i = to; i != from - 1; i--) {
		json_generate_one_record(g, NULL, b, i, 0);
		yajl_gen_get_buf(g, &ybuf, &len);
		fwrite(ybuf, len, 1, stdout);
		yajl_gen_clear(g);
	}

	yajl_gen_array_close(g);
	yajl_gen_get_buf(g, &ybuf, &len);
	fwrite(ybuf, len, 1, stdout);
	yajl_gen_free(g);
}

void respond_blergref_list(struct blergref * results, int i) {
	const unsigned char *ybuf;
	unsigned int len;
	struct blerg *b;
	yajl_gen g;

	i--;

	printf("Content-type: application/json\r\n\r\n");
	g = yajl_gen_alloc(&yajl_c, NULL);

	yajl_gen_array_open(g);

	while (i >= 0) {
		b = blerg_open(results[i].author);
		if (b != NULL) {
			json_generate_one_record(g, results[i].author, b, results[i].record, 0);
			blerg_close(b);
		}
		yajl_gen_get_buf(g, &ybuf, &len);
		fwrite(ybuf, len, 1, stdout);
		yajl_gen_clear(g);

		i--;
	}

	yajl_gen_array_close(g);
	yajl_gen_get_buf(g, &ybuf, &len);
	fwrite(ybuf, len, 1, stdout);
	yajl_gen_free(g);
}

int main(int argc, char *argv[]) {
	char *path;
	char *request_method;
	int ret, len;
	struct url_info info;
	struct auth_cookie ac;
	char *data;

	if (!blerg_init())
		exit(1);

	if (cgi_init() != CGIERR_NONE)
		exit(0);

	path = getenv("PATH_INFO");
	if (path == NULL) {
		respond_404();
		exit(0);
	}
	request_method = getenv("REQUEST_METHOD");
	if (request_method == NULL) {
		fprintf(stderr, "Request method is null!?\n");
		exit(0);
	}

	if (strncmp(path, "/get", 4) == 0 && strlen(path) > 4) {
		if (strncmp(request_method, "GET", 4) != 0) {
			respond_405();
			exit(0);
		}

		if (path[4] != '/') {
			respond_404();
			exit(0);
		}

		ret = parse_url_info(path + 5, &info);
		if ((ret & URL_INFO_NAME) == 0) {
			respond_404();
			exit(0);
		}

		if (!blerg_exists(info.name)) {
			respond_404();
			exit(0);
		}

		struct blerg *b = blerg_open(info.name);

		if ((ret & URL_INFO_RECORD) && (ret & URL_INFO_RECORD_TO)) {
			respond_for_range(b, info.record, info.record_to);
		} else if (ret & URL_INFO_RECORD) {
			ret = blerg_fetch(b, info.record, &data, &len);

			if (ret == 0) {
				respond_404();
				exit(0);
			}
			respond_simple_data(data, len);
		} else {
			uint64_t record_count, from, to;
			record_count = blerg_get_record_count(b);
			if (record_count == 0) {
				respond_simple_data("[]", 2);
			} else {
				to = record_count - 1;
				from = (record_count > 50 ? to - 49 : 0);
				respond_for_range(b, from, to);
			}
		}

		blerg_close(b);
	} else if (strncmp(path, "/tag", 4) == 0 && strlen(path) > 4) {
		if (strcmp(request_method, "GET") != 0) {
			respond_405();
			exit(0);
		}

		if (path[4] != '/') {
			respond_404();
			exit(0);
		}

		ret = parse_url_info(path + 5, &info);
		if ((ret & URL_INFO_NAME) == 0) {
			respond_404();
			exit(0);
		}

		if (info.name[0] == 'H')
			info.name[0] = '#';
		if (!tag_exists(info.name)) {
			respond_404();
			exit(0);
		}

		int recs = 50;
		struct blergref *taglist = tag_list(info.name, 0, &recs, -1);

		if (recs == 0) {
			respond_simple_data("[]", 2);
		} else {
			respond_blergref_list(taglist, recs);
		}
	} else if (strncmp(path, "/put", 4) == 0) {
		if (strcmp(request_method, "POST") != 0) {
			respond_405();
			exit(0);
		}

		if (!check_auth(&ac))
			exit(0);

		if (path[4] == '/') {
			respond_404();
			exit(0);
		}

		const char *data = cgi_getentrystr("data");
		if (data == NULL || data[0] == 0) {
			respond_JSON_Failure();
			exit(0);
		}

		struct blerg *b = blerg_open(ac.name);
		if (b == NULL) {
			respond_JSON_Failure();
			exit(0);
		}
		ret = blerg_store(b, data, strlen(data));
		blerg_close(b);
		if (ret == -1) {
			respond_JSON_Failure();
			exit(0);
		}

		respond_JSON_Success();
	} else if (strncmp(path, "/info", 5) == 0) {
		if (strcmp(request_method, "GET") != 0) {
			respond_405();
			exit(0);
		}

		if (path[5] != '/') {
			respond_404();
			exit(0);
		}

		ret = parse_url_info(path + 6, &info);
		if ((ret & URL_INFO_NAME) == 0) {
			respond_404();
			exit(0);
		}

		if (!blerg_exists(info.name)) {
			respond_404();
			exit(0);
		}

		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, (unsigned char *)"record_count", 12);
		snprintf(number, 21, "%llu", record_count);
		yajl_gen_string(g, (unsigned char *)number, strlen(number));
		yajl_gen_map_close(g);

		respond_yajl(g);

		yajl_gen_free(g);
	} else if (strncmp(path, "/create", 8) == 0) {
		if (strcmp(request_method, "POST") != 0) {
			respond_405();
			exit(0);
		}

		const char *username = cgi_getentrystr("username");
		const char *password = cgi_getentrystr("password");
		if (username == NULL || username[0] == 0 ||
		    password == NULL || password[0] == 0) {
			respond_JSON_Failure();
			exit(0);
		}

		if (blerg_exists(username)) {
			respond_JSON_Failure();
			exit(0);
		}

		struct blerg *b = blerg_open(username);
		if (b != NULL) {
			blerg_close(b);
			auth_set_password(username, password);
			
			respond_JSON_Success();
		} else {
			respond_JSON_Failure();
		}
	} else if (strncmp(path, "/login", 7) == 0) {
		if (strcmp(request_method, "POST") != 0) {
			respond_405();
			exit(0);
		}

		const char *username = cgi_getentrystr("username");
		const char *password = cgi_getentrystr("password");
		if (username == NULL || username[0] == 0 ||
		    password == NULL || password[0] == 0) {
			respond_JSON_Failure();
			exit(0);
		}

		char *token = auth_login(username, password);
		if (token == NULL) {
			respond_JSON_Failure();
			exit(0);
		}

		printf("Set-Cookie: auth=%s/%s\r\n", username, token);
		free(token);

		respond_JSON_Success();
	} else if (strncmp(path, "/logout", 8) == 0) {
		if (strcmp(request_method, "POST") != 0) {
			respond_405();
			exit(0);
		}

		if (!check_auth(&ac))
			exit(0);

		auth_logout(ac.name, ac.token);
		printf("Set-Cookie: auth=X; Expires=Thu, 01 Jan 1970 00:00:00 GMT\r\n");
		respond_JSON_Success();
	} else if (strncmp(path, "/subscribe", 10) == 0) {
		if (!check_auth(&ac))
			exit(0);

		if (path[10] != '/') {
			respond_404();
			exit(0);
		}

		ret = parse_url_info(path + 11, &info);
		if ((ret & URL_INFO_NAME) == 0) {
			respond_404();
			exit(0);
		}

		const char *subscribed = cgi_getentrystr("subscribed");

		if (strncmp(subscribed, "true", 4) == 0) {
			subscription_add(ac.name, info.name);
		} else if (strncmp(subscribed, "false", 5) == 0) {
			subscription_remove(ac.name, info.name);
		} else {
			respond_JSON_Failure();
			exit(0);
		}
		respond_JSON_Success();
	} else if (strncmp(path, "/feed", 6) == 0) {
		if (!check_auth(&ac))
			exit(0);

		int recs = 50;
		struct blergref *feedlist = subscription_list(ac.name, 0, &recs, -1);

		if (recs == 0) {
			respond_simple_data("[]", 2);
		} else {
			respond_blergref_list(feedlist, recs);
		}
	} else if (strncmp(path, "/status", 7) == 0) {
		if (!check_auth(&ac))
			exit(0);

		if (strncmp(request_method, "POST", 4) == 0) {
			const char *clear = cgi_getentrystr("clear");

			if (clear != NULL) {
				struct blerg *b = blerg_open(ac.name);
				if (strncmp(clear, "feed", 4) == 0) {
					blerg_set_subscription_mark(b);
				} else if (strncmp(clear, "mentioned", 9) == 0) {
					blerg_set_status(b, BLERGSTATUS_MENTIONED, 0);
				}
				blerg_close(b);
				respond_JSON_Success();
			}
		} else if (strncmp(request_method, "GET", 3) == 0) {
			yajl_gen g;

			if (path[7] == 0) {  /* No username */
				g = yajl_gen_alloc(&yajl_c, NULL);
				yajl_gen_map_open(g);

				struct blerg *b = blerg_open(ac.name);
				uint64_t subscription_mark = blerg_get_subscription_mark(b);
				int mentioned = blerg_get_status(b, BLERGSTATUS_MENTIONED);
				blerg_close(b);

				yajl_gen_string(g, (unsigned char *)"feed_new", 8);
				yajl_gen_integer(g, subscription_count_items(ac.name) - subscription_mark);

				yajl_gen_string(g, (unsigned char *)"mentioned", 9);
				yajl_gen_bool(g, mentioned);

				yajl_gen_map_close(g);
				respond_yajl(g);
				yajl_gen_free(g);
			} else {  /* with username */
				g = yajl_gen_alloc(&yajl_c, NULL);
				yajl_gen_map_open(g);

				yajl_gen_string(g, (unsigned char *)"subscribed", 10);
				ret = parse_url_info(path + 8, &info);
				if ((ret & URL_INFO_NAME) == 1) {
					yajl_gen_bool(g, is_subscribed(ac.name, info.name));
				} else {
					yajl_gen_bool(g, 0);
				}

				yajl_gen_map_close(g);
				respond_yajl(g);
				yajl_gen_free(g);
			}
		} else {
			respond_405();
			exit(0);
		}
	} else if (strncmp(path, "/passwd", 7) == 0) {
		if (!check_auth(&ac))
			exit(0);

		const char *password = cgi_getentrystr("password");
		const char *new_password = cgi_getentrystr("new_password");
		if (password == NULL || new_password == NULL) {
			respond_JSON_Failure();
		} else {
			if (auth_check_password(ac.name, password)) {
				auth_set_password(ac.name, new_password);
				respond_JSON_Success();
			} else {
				respond_JSON_Failure();
			}
		}
	} else {
		respond_404();
		exit(0);
	}

	cgi_quit();

	return 0;
}