aboutsummaryrefslogtreecommitdiff
/**
 * part of Hydrilla
 * Serving data queries over HTTP using libmicrohttpd.
 *
 * Copyright (C) 2021 Wojtek Kosior
 * Redistribution terms are gathered in the `copyright' file.
 *
 * This file is based on Christian Grothoff's public comain code from
 * `doc/examples/logging.c' in libmicrohttpd source tree.
 */

#define _POSIX_C_SOURCE 199506L /* sigwait() */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>

#include <microhttpd.h>

#include "scriptbase.h"

#define PORT 10111

#define ARRLEN(array) (sizeof(array) / sizeof(*array))

static struct MHD_Response *default_response, *error_response,
	*not_found_response;

static struct {
	struct MHD_Response **response;
	const char *const text;
} static_responses[] = {
	{&default_response,   "<html><body>Nothing to see here</body></html>"},
	{&error_response,     "<html><body>Error occured</body></html>"},
	{&not_found_response, "<html><body>Not found</body></html>"}
};

static int add_CORS_header(struct MHD_Response *response)
{
	return -1 * (MHD_add_response_header(response,
					     "Access-Control-Allow-Origin",
					     "*") == MHD_NO);
}

typedef char *(*request_handler_t)(const char *queried,
				   struct scriptbase *base);

static struct {
	const char *path;
	request_handler_t request_handler;
} resources[] = {
		 {"/script",  get_script_json},
		 {"/bag",     get_bag_json},
		 {"/pattern", get_pattern_json},
		 {"/query",   get_page_query_json}
};

struct request_handling {
	struct scriptbase *base;
	request_handler_t handler;
	char *json_response;
	int error_number;
};

static int handle_query_argument(void *cls, enum MHD_ValueKind kind,
				 const char *key, const char *value)
{
	struct request_handling *handling = cls;

	if (strcmp(key, "n")) {
		fprintf(stderr, "Unknown argument: \"%s\" = \"%s\"\n",
			key, value);
		return MHD_YES;
	}

	errno = 0;

	handling->json_response = handling->handler(value, handling->base);

	handling->error_number = errno;

	return MHD_NO;
}

static int answer_with(struct MHD_Connection *connection,
		       request_handler_t handler, struct scriptbase *base)
{
	struct request_handling handling = {base, handler, NULL, EINVAL};
	struct MHD_Response *response;
	int retval;

	MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,
				  handle_query_argument, &handling);

	if (!handling.json_response) {
		if (handling.error_number)
			goto send_error;
		goto send_not_found;
	}

	response = MHD_create_response_from_buffer
		(strlen(handling.json_response), handling.json_response,
		 MHD_RESPMEM_MUST_FREE);
	if (!response || add_CORS_header(response))
		goto send_error;

	retval = MHD_queue_response(connection, MHD_HTTP_OK, response);
	MHD_destroy_response(response);

	return retval;

send_not_found:
	return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND,
				  not_found_response);

send_error:
	free(handling.json_response);
	return MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
				  error_response);
}

static int answer(void *data,
		  struct MHD_Connection *connection,
		  const char *url,
		  const char *method,
		  const char *version,
		  const char *upload_data,
		  size_t *upload_data_size,
		  void **ptr)
{
	static int aptr;
	int i;
	struct scriptbase *base = data;

	printf("New %s request for %s using version %s\n", method, url, version);

	if (strcmp(method, "GET"))
		return MHD_NO;

	if (&aptr != *ptr) {
		/* do never respond on first call */
		*ptr = &aptr;
		return MHD_YES;
	}
	*ptr = NULL; /* reset when done */

	for (i = 0; i < ARRLEN(resources); i++) {
		if (strcmp(resources[i].path, url))
			continue;

		return answer_with(connection, resources[i].request_handler,
				   base);
	}

	return MHD_queue_response(connection, MHD_HTTP_OK, default_response);
}

int getchar(void);

int serve_scriptbase(struct scriptbase *base, bool wait_for_sigterm)
{
	int i;
	struct MHD_Daemon *daemon;
	sigset_t sigterm_set;
	int signal_number; /* only written, not read */
	int retval = -1;

	if (init_url_lookup_regex())
	  return -1;

	for (i = 0; i < ARRLEN(static_responses); i++) {
		*static_responses[i].response = MHD_create_response_from_buffer
			(strlen(static_responses[i].text),
			 (char*) static_responses[i].text,
			 MHD_RESPMEM_PERSISTENT);

		if (!*static_responses[i].response ||
		    add_CORS_header(*static_responses[i].response))
			goto free_resources;
	}

	daemon = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL,
				  NULL, &answer, (void*) base, MHD_OPTION_END);
	if (!daemon)
		goto free_resources;

	if (wait_for_sigterm) {
		sigemptyset(&sigterm_set);
		sigaddset(&sigterm_set, SIGTERM);
		sigwait(&sigterm_set, &signal_number);
	} else {
		getchar();
	}

	MHD_stop_daemon(daemon);

	retval = 0;

free_resources:
	for (i = 0; i < ARRLEN(static_responses); i++) {
		if (*static_responses[i].response)
			MHD_destroy_response(*static_responses[i].response);
		*static_responses[i].response = NULL;
	}

	destroy_url_lookup_regex();

	return retval;
}