💾 Archived View for gemini.thededem.de › lc19 › src › src › srv.c captured on 2021-12-03 at 14:04:38.

View Raw

More Information

-=-=-=-=-=-=-

/* Copyright 2020, 2021 Lukas Wedeking
 *
 * This file is part of LC19.
 *
 * LC19 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LC19 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LC19.  If not, see <https://www.gnu.org/licenses/>.
 */

#define _XOPEN_SOURCE 500
#define _DEFAULT_SOURCE

#include "../include/srv.h"
#include "../include/util.h"

#include<assert.h>
#include<dirent.h>
#include<errno.h>
#include<fcntl.h>
#include<libgen.h>
#include<limits.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int
srv_response_set_status(int status)
{
	extern struct Response response;
	if (status != 10 && status != 11 && status != 20 && status != 30
			&& status != 31 && status != 40 && status != 41
			&& status != 42 && status != 43 && status != 44
			&& status != 50 && status != 51 && status != 52
			&& status != 53 && status != 59 && status != 60
			&& status != 61 && status != 62 && status != 1
			&& status != 2 && status != 3 && status != 4
			&& status != 5 && status != 6) {
		log_msg(LOGLEVEL_ERROR, "Illegal status code %d.", status);
		response.status = 0;
		return 0;
	}
	response.status = status;
	if (response.meta[0] != 0x0) {
		response.state = RESPONSESTATE_READY;
	}
	return 1;
}

int
srv_response_set_meta(const char *meta)
{
	extern struct Response response;
	int utf8_index = 0;
	for (int i=0; i<1024; i++) {
		if (utf8_index > 0) {
			/*
			 * If a multi-byte UTF-8 character is processed, check
			 * that the follow up characters match the expected
			 * value. Ie. 10xxxxxx in binary representation.
			 */
			if ((meta[i] & 0xc0) != 0x80) {
				log_msg(LOGLEVEL_ERROR, "Illegal UTF-8 "
						"character in meta string: %s",
						meta);
				response.meta[0] = 0x0;
				return 0;
			}
			utf8_index--;
		} else if ((meta[i] & 0xe0) == 0xc0) {
			/* The current byte is the beginning of a two byte UTF-8
			 * character. */
			utf8_index = 1;
		} else if ((meta[i] & 0xf0) == 0xe0) {
			/* The current byte is the beginning of a three byte UTF-8
			 * character. */
			utf8_index = 2;
		} else if ((meta[i] & 0xf8) == 0xf0) {
			/* The current byte is the beginning of a four byte UTF-8
			 * character. */
			utf8_index = 3;
		} else if ((meta[i] & 0x80) != 0x0) {
			log_msg(LOGLEVEL_ERROR, "Illegal UTF-8 character in "
					"meta string: %s", meta);
			response.meta[0] = 0x0;
			return 0;
		}
		if (meta[i] == 0x0 || meta[i] == '\r' || meta[i] == '\n') {
			response.meta[i] = 0x0;
			break;
		}
		response.meta[i] = meta[i];
		if (i == 1024 && meta[i+1] != 0x0) {
			log_msg(LOGLEVEL_ERROR, "Meta exceeds maximum 1024 "
					"bytes: %s", meta);
			response.meta[0] = 0x0;
			return 0;
		}
	}
	if (response.status != 0) {
		response.state = RESPONSESTATE_READY;
	}
	return 1;
}

void
srv_response_set_data(FILE *data, enum ResourceType type)
{
	assert(data != NULL);
	extern struct Response response;
	if (response.data != NULL) {
		fclose(response.data);
	}
	response.data = data;
	response.type = type;
	if (type == RESOURCETYPE_CGI) {
		response.state = RESPONSESTATE_READY;
	}
}

void
srv_response_flush()
{
	extern struct Response response;
	extern struct Configuration configuration;
	char *buffer = NULL;
	size_t read_size;
	if (response.state == RESPONSESTATE_READY) {
		if (response.type == RESOURCETYPE_PLAIN) {
			printf("%d %s\r\n", response.status, response.meta);
			fflush(stdout);
		}
		if ((response.status == 20 && response.data != NULL)
				|| response.type == RESOURCETYPE_CGI) {
			buffer = calloc(configuration.file_chunk_size,
					sizeof(char));
			while ((read_size = fread(buffer, sizeof(char),
							configuration.file_chunk_size,
							response.data))) {
				fwrite(buffer, sizeof(char), read_size, stdout);
			}
			free(buffer);
		}
		if (response.data != NULL) {
			fclose(response.data);
			response.data = NULL;
			if (response.type != RESOURCETYPE_CGI
					&& response.status != 20) {
				log_msg(LOGLEVEL_INFO, "Reponse with status "
						"code other than 20 does "
						"contain data.");
			}
		}
		response.state = RESPONSESTATE_FLUSHED;
	} else if (response.state != RESPONSESTATE_FLUSHED) {
		printf("50 Internal server error\r\n");
	}
	fflush(stdout);
}

char*
srv_resource_aux_path(const char *path, const char *extension)
{
	assert(path != NULL);
	assert(extension != NULL);

	size_t path_len = strlen(path);
	size_t ext_len = strlen(extension);
	/*
	 * The path to the auxilliary file is three characters longer than the
	 * file path plus the extension, because of the '.' before the filename,
	 * the '.' before the extension and the zero termination.
	 */
	size_t len = path_len + ext_len + 3;
	char *aux_path = calloc(len, sizeof(char));

	strcpy(aux_path, path);

	/*
	 * Find the filename part of the path, so we can preprend the filename
	 * with a dot.
	 */
	char *filename = strrchr(aux_path, '/');
	if (filename == NULL) {
		free(aux_path);
		return NULL;
	}
	filename++; /* Move the pointer from the slash to the first character of
		       the filename. */
	char tmp_r = 0;
	char tmp_w = 0;
	for (size_t i = 0; i == 0 || tmp_w != 0x0; i++) {
		tmp_r = filename[i];
		if (i == 0) {
			filename[i] = '.';
		} else {
			filename[i] = tmp_w;
		}
		tmp_w = tmp_r;
	}

	aux_path[path_len + 1] = '.';

	strcpy(aux_path + path_len + 2, extension);

	return aux_path;
}

char*
srv_resource_aux_content(const char *path, const char *type, size_t size)
{
	assert(size);

	char *meta = NULL;
	char *aux_path = srv_resource_aux_path(path, type);
	assert(aux_path);

	FILE *file = NULL;
	if (!(file = fopen(aux_path, "r"))) {
		goto cleanup;
	}
	assert(file);

	meta = calloc(size + 1, sizeof(char));
	if (fgets(meta, size, file) == NULL) {
		free(meta);
		meta = NULL;
	}
	fclose(file);
cleanup:
	free(aux_path);
	return meta;
}

int
srv_resource_mime_from_file(const char *path)
{
	char *meta = srv_resource_aux_content(path, "mime", 1025);
	int success = meta != NULL && srv_response_set_status(20) &&
		srv_response_set_meta(meta);
	free(meta);
	return success;
}

int
srv_response_redirect(const char *path)
{
	char *meta = srv_resource_aux_content(path, "redirect", 1027);
	int status = 30;
	size_t offset = 0;
	if (meta != NULL && strncmp(meta, "p ", 2) == 0) {
		status = 31;
		offset = 2;
	}
	int success = meta != NULL && srv_response_set_status(status) &&
		srv_response_set_meta(meta + offset);
	free(meta);
	return success;
}

char*
srv_find_file_ext(const char *path)
{
	char *file_ext = strrchr(path, '.');
	/*
	 * Everything after the last dot is interpreted as extension as long as
	 * there is no slash after the last dot.
	 */
	if (file_ext == NULL || strrchr(file_ext, '/') != NULL) {
		return NULL;
	}
	file_ext++; /* Move the pointer from the dot before the extension to the
		       extension's first character. */
	return file_ext;
}

int
srv_response_meta_from_mimedb(const char *path, const char *mimedb)
{
	char *file_ext = srv_find_file_ext(path);
	if (file_ext == NULL) {
		return 0;
	}
	size_t ext_len = strlen(file_ext);

	FILE *file = NULL;
	if (!(file = fopen(mimedb, "r"))) {
		log_msg(LOGLEVEL_ERROR, "Unable to open MIME type file at %s.", mimedb);
		return 0;
	}
	char buf[1025] = { 0x0 };
	while (fgets(buf, 1025, file)) {
		if (buf[0] == '#') {
			continue;
		}
		/*
		 * The MIME types file consists of MIME type definitions per
		 * each line. First the name of the MIME type is given, followed
		 * by a list of file extensions. All elements on a line are
		 * separated by whitespaces.
		 */
		size_t type_end = 0;
		size_t ext_idx = 0;
		for (size_t i = 0; buf[i] != 0x0 && i < 1025; i++) {
			if (type_end == 0 && (buf[i] == ' ' || buf[i] == '\t')) {
				type_end = i;
			}
			if (type_end == 0) {
				continue;
			}
			if (ext_idx == 0 && buf[i] != ' ' && buf[i] != '\t') {
				ext_idx = i;
			}
			if (ext_idx != 0 && (buf[i + 1] == 0x0
						|| buf[i + 1] == '\r'
						|| buf[i + 1] == '\n'
						|| buf[i + 1] == ' '
						|| buf[i + 1] == '\t')) {
				size_t len = i + 1 - ext_idx;
				if (ext_len == len && strncmp(file_ext,
							&buf[ext_idx],
							len) == 0) {
					buf[type_end] = 0x0;
					fclose(file);
					return srv_response_set_status(20)
						&& srv_response_set_meta(buf);
				}
				ext_idx = 0;
			}
		}
	}
	fclose(file);
	return 0;
}

int
srv_response_meta_from_ext(const char *path)
{
	char *file_ext = srv_find_file_ext(path);
	if (file_ext == NULL) {
		return 0;
	}

	if (strncmp(file_ext, "gmi", 3) == 0
			|| strncmp(file_ext, "gemini", 6) == 0) {
		return srv_response_set_status(20)
			&& srv_response_set_meta("text/gemini");
	}
	return 0;
}

void
srv_response_dir_index(const char *path, struct Url *url)
{
	srv_response_set_status(20);
	srv_response_set_meta("text/gemini");
	srv_response_flush();
	size_t last_slash_pos = 0;
	for (size_t i = 0; url->path[i] != 0x0; i++) {
		if (url->path[i] == '/' && url->path[i + 1] != 0x0) {
			last_slash_pos = i;
		}
	}
	printf("# Index of %s\n", url->path + last_slash_pos);
	char *name = NULL;
	char *enc_path = NULL;
	DIR *dir = opendir(path);
	struct dirent *de = NULL;
	while ((de = readdir(dir))) {
		if (de->d_name[0] == '.') {
			continue;
		}
		name = url_pct_encode_path(de->d_name, 1);
		enc_path = url_pct_encode_path(url->path, 1);
		if (de->d_type == DT_DIR) {
			printf("=> %s%s/ %s/\n", enc_path, name, de->d_name);
		} else {
			printf("=> %s%s %s\n", enc_path, name, de->d_name);
		}
		free(enc_path);
		free(name);
	}
	closedir(dir);
}

int
srv_response_url_error(const struct Url *url)
{
	assert(url->port != NULL);
	int port = 1965;
	sscanf(url->port, "%d", &port);
	if (url->scheme == NULL) {
		srv_response_set_status(59);
		srv_response_set_meta("Missing scheme.");
		return 1;
	} else if (strcmp(url->scheme, "gemini") != 0) {
		srv_response_set_status(53);
		srv_response_set_meta("Invalid scheme.");
		return 1;
	} else if (url->userinfo != NULL) {
		srv_response_set_status(59);
		srv_response_set_meta("Userinfo not allowed.");
		return 1;
	} else if (url->host == NULL) {
		srv_response_set_status(59);
		srv_response_set_meta("Missing host.");
		return 1;
	} else if (port < 0 || port > 65535) {
		srv_response_set_status(53);
		srv_response_set_meta("Bad port.");
		return 1;
	} else if (url->junk != NULL) {
		if (strcmp(url->junk, "too long") == 0) {
			srv_response_set_status(59);
			srv_response_set_meta("Invalid request termination.");
		} else {
			srv_response_set_status(59);
			srv_response_set_meta("Invalid URL.");
		}
		return 1;
	} else if (!srv_check_resource_path(url->path)) {
		srv_response_set_status(51);
		srv_response_set_meta("Resource not available.");
		log_msg(LOGLEVEL_DEBUG,
				"Respond 51 because path is invalid: %s",
				url->path);
	}

	return 0;
}

int
srv_check_resource_path(const char *path)
{
	short int path_depth = 0;
	for (size_t i = 0; path[i] != 0x0; i++) {
		if (i > 0 && path[i - 1] == '/') {
			if (path[i] == '.' && path[i + 1] == '.'
					&& (path[i + 2] == '/'
						|| path[i + 2] == 0x0)) {
				path_depth--;
			} else if (!(path[i] == '.' && (path[i + 1] == '/'
							|| path[i + 1] == 0x0))
					&& path[i] != '/') {
				path_depth++;
			}
		}
		/*
		 * Check that the path does not leave the root
		 * directory.
		 */
		if (path_depth < 0) {
			return 0;
		}
		/* We do not want to serve hidden files or content of
		 * hidden directories. The only exception to this is
		 * /.well-known/ */
		if (path[i] == '/' && path[i + 1] == '.'
				&& !(path[i + 2] == '/' || path[i + 2] == 0x0)
				&& !(path[i + 2] == '.' && (path[i + 3] == '/'
						|| path[i + 3] == 0x0))
				&& !(i == 0 && strncmp(&path[i + 1],
						".well-known/", 12) == 0)) {
			return 0;
		}
	}
	return 1;
}

char*
srv_resource_path(const char *data_dir, const struct Url *url,
		enum ResourceType *type, struct stat *st)
{
	assert(data_dir != NULL);
	assert(url != NULL);
	assert(url->host != NULL);
	assert(url->port != NULL);
	assert(url->path != NULL);

	char *real_path = NULL;
	char *raw_path = NULL;
	char *last_slash = NULL;
	size_t path_index = 0;
	size_t data_end = 0;
	size_t host_end = 0;
	size_t port_end = 0;
	size_t path_length = 3; /* This includes the 0 termination and the slash
				   characters inserted between data directory
				   path and host directory and host directory
				   and port. */
	path_length += strlen(data_dir);
	path_length += strlen(url->host);
	path_length += strlen(url->port);
	if (url->path != NULL) {
		path_length += strlen(url->path);
	} else {
		path_length += 1;
	}
	raw_path = calloc(path_length, sizeof(char));

	/* Start the path with the path to the data directory. */
	strcpy(raw_path, data_dir);
	path_index = strlen(data_dir); /* Tracks where in raw_path the
						 next part is inserted. */
	data_end = path_index;

	/* Add to the path the host directory. */
	raw_path[path_index] = '/';
	path_index += 1;
	strcpy(raw_path + path_index, url->host);
	path_index += strlen(url->host);
	host_end = path_index;

	/* Add to the path the port directory. */
	raw_path[path_index] = '/';
	path_index += 1;
	strcpy(raw_path + path_index, url->port);
	path_index += strlen(url->port);
	port_end = path_index;

	/* Add the request URI's path to the resource path. */
	strcpy(raw_path + path_index, url->path);

	/* Remove all symbolic links and relative parts from the path. */
	real_path = realpath(raw_path, NULL);

	/*
	 * realpath() returns NULL if the path does not reference an existing
	 * file or directory. We need to give a fine grained response, at which
	 * point the path resolution actually failed.
	 */
	if (real_path == NULL) {
		/*
		 * There might be a reponse file defining some custom response
		 * (eg. a redirect), that does not require a resource.
		 */
		if (srv_response_redirect(raw_path)) {
			goto cleanup;
		}

		last_slash = strrchr(raw_path, '/');
		while (real_path == NULL && last_slash != NULL
				&& last_slash != &raw_path[port_end]) {
			*last_slash = 0x0;
			if (stat(raw_path, st) == 0 && S_ISREG(st->st_mode)
					&& (st->st_mode & S_IXUSR)) {
				real_path = realpath(raw_path, NULL);
				*type = RESOURCETYPE_CGI;
				goto cleanup;
			}

			last_slash = strrchr(raw_path, '/');
		}

		/* Check if the port directory exists. */
		raw_path[port_end] = 0x0;
		real_path = realpath(raw_path, NULL);
		if (real_path != NULL) {
			srv_response_set_status(51);
			srv_response_set_meta("Resource not available.");
			raw_path[port_end] = '/';
			log_msg(LOGLEVEL_DEBUG, "Respond 51 because resource "
					"is not available: %s", raw_path);
			free(real_path);
			real_path = NULL;
			goto cleanup;
		}

		/* Check if the host directory exists. */
		raw_path[host_end] = 0x0;
		real_path = realpath(raw_path, NULL);
		if (real_path != NULL) {
			srv_response_set_status(53);
			srv_response_set_meta("Bad port.");
			raw_path[host_end] = '/';
			log_msg(LOGLEVEL_DEBUG, "Respond 53 because port dir "
					"is missing: %s", raw_path);
			free(real_path);
			real_path = NULL;
			goto cleanup;
		}

		/* Check if the data directory exists. */
		raw_path[data_end] = 0x0;
		real_path = realpath(raw_path, NULL);
		if (real_path != NULL) {
			srv_response_set_status(53);
			srv_response_set_meta("Unknown host.");
			raw_path[data_end] = '/';
			log_msg(LOGLEVEL_DEBUG, "Respond 53 because host dir "
					"is missing: %s", raw_path);
			free(real_path);
			real_path = NULL;
			goto cleanup;
		}
		log_msg(LOGLEVEL_ERROR, "Unable to open data directory: %s",
				raw_path);
	} else if (stat(real_path, st) == 0 && S_ISREG(st->st_mode)
			&& (st->st_mode & S_IXUSR)) {
		*type = RESOURCETYPE_CGI;
	}

cleanup:
	free(raw_path);
	return real_path;
}

void
srv_resource_append_mime_parameter(const char *param)
{
	extern struct Response response;
	char *metaptr = NULL;
	char pname_meta[1025] = { 0x0 };
	size_t pname_meta_len = 0;

	char pname[1025] = { 0x0 };
	size_t pname_len = util_mime_next_param(param, pname, 1025);

	size_t append_pos = 0;

	if (pname_len == 0) {
		return;
	}

	metaptr = strchr(response.meta, ';');
	if (metaptr == NULL) {
		metaptr = strchr(response.meta, 0x0);
	}
	assert(metaptr);
	metaptr++;

	while ((pname_meta_len = util_mime_next_param(metaptr, pname_meta,
					1025))) {
		if (pname_len == pname_meta_len
				&& strcmp(pname, pname_meta)) {
			return;
		}
		metaptr = strchr(response.meta, ';');
		if (metaptr == NULL) {
			metaptr = strchr(response.meta, 0x0);
		}
		assert(metaptr);
	}

	append_pos = strlen(response.meta);
	if (append_pos < 1023) {
		response.meta[append_pos] = ';';
		response.meta[append_pos + 1] = 0x0;
	}
	append_pos++;
	for (size_t i = 0; append_pos < 1024 && param[i] != 0x0
			&& param[i] != '\r' && param[i] != '\n'; i++) {
		response.meta[append_pos] = param[i];
		append_pos++;
	}
	response.meta[append_pos] = 0x0;
}

void
srv_resource_update_mime_parameters(const char *path)
{
	extern struct Response response;
	extern struct Configuration configuration;

	if (response.status != 20 || strlen(response.meta) == 0) {
		return;
	}

	char mimetype[1025] = { 0x0 };
	size_t mimetype_len = util_mime_type(response.meta, mimetype, 1024);
	assert(mimetype_len < 1025);
	for (size_t i = 0; i < mimetype_len; i++) {
		/*
		 * Replace all characters that are not alphanumeric, '.', '+' or
		 * '-' with '.' to get the filename for the mime type.
		 */
		if (!(mimetype[i] >= 'a' && mimetype[i] <= 'z')
				&& !(mimetype[i] >= 'A' && mimetype[i] <= 'Z')
				&& !(mimetype[i] >= '0' && mimetype[i] <= '9')
				&& mimetype[i] != '.' && mimetype[i] != '+'
				&& mimetype[i] != '-') {
			mimetype[i] = '.';
		}
	}

	/*
	 * Increase the size of dirpath by 12. This contains the zero
	 * termination as well as the possibility to add ".mimeparam" to the
	 * path.
	 */
	char *mimepath = calloc(strlen(path) + mimetype_len + 12, sizeof(char));
	strcpy(mimepath, path);
	char *dir_delim = NULL;

	FILE *file = NULL;
	char buf[1025] = { 0x0 };
	do {
		dir_delim = strrchr(mimepath, '/');
		dir_delim[1] = '.';
		strcpy(dir_delim + 2, mimetype);
		strcpy(dir_delim + 2 + mimetype_len, ".mimeparam");
		dir_delim[12 + mimetype_len] = 0x0;

		if (!(file = fopen(mimepath, "r"))) {
			*dir_delim = 0x0;
			continue;
		}
		/*
		 * Only read 1024 bytes per line, because the meta string cannot
		 * exceed that size.
		 */
		while (fgets(buf, 1025, file) != NULL) {
			srv_resource_append_mime_parameter(buf);
		}
		fclose(file);

		*dir_delim = 0x0;
		/*
		 * TODO Implement a better boundary mechanism for going up the
		 * directory hierarchy.
		 */
	} while (strlen(mimepath) > strlen(configuration.data_dir));
	free(mimepath);
}

void
srv_response_resource(struct Url *url)
{
	extern struct Configuration configuration;

	assert(configuration.data_dir != NULL);
	assert(configuration.mimedb != NULL);
	assert(url != NULL);
	assert(url->path != NULL);
	assert(url->path_orig != NULL);
	assert(url->path[0] != 0x0);
	assert(url->path_orig[0] != 0x0);

	enum ResourceType type = RESOURCETYPE_PLAIN;
	struct stat st;
	char *path = srv_resource_path(configuration.data_dir, url, &type,
			&st);
	char *enc_path = NULL;
	char *idx_path = NULL;
	FILE *res = NULL;
	size_t len = 0;
	char meta[1025] = {0x0};
	size_t meta_idx = 0;

	if (path == NULL) {
		goto cleanup;
	}

	/*
	 * If the encoded, normalized path differs from the original path,
	 * redirect to the normalized path.
	 */
	enc_path = url_pct_encode_path(url->path, 0);
	len = strlen(enc_path);
	if (strcmp(enc_path, url->path_orig) != 0 || (S_ISDIR(st.st_mode)
				&& enc_path[len - 1] != '/')) {
		srv_response_set_status(31);
		if (url->scheme != NULL) {
			meta_idx += util_strcpy(meta, url->scheme, 1025);
			meta_idx += util_strcpy(meta+meta_idx, ":",
					1025-meta_idx);
		}
		if (url->host != NULL) {
			meta_idx += util_strcpy(meta+meta_idx, "//",
					1025-meta_idx);
			meta_idx += util_strcpy(meta+meta_idx, url->host,
					1025-meta_idx);
		}
		if (url->port != NULL && strcmp(url->port, "1965") != 0) {
			meta_idx += util_strcpy(meta+meta_idx, ":",
					1025-meta_idx);
			meta_idx += util_strcpy(meta+meta_idx, url->port,
					1025-meta_idx);
		}
		if (S_ISDIR(st.st_mode) && enc_path[len - 1] != '/') {
			meta_idx += util_strcpy(meta+meta_idx, enc_path,
					1025-meta_idx);
			meta_idx += util_strcpy(meta+meta_idx, "/",
					1025-meta_idx);
		} else {
			meta_idx += util_strcpy(meta+meta_idx, enc_path,
					1025-meta_idx);
		}
		if (url->query != NULL) {
			meta_idx += util_strcpy(meta+meta_idx, "?",
					1025-meta_idx);
			meta_idx += util_strcpy(meta+meta_idx, url->query,
					1025-meta_idx);
		}
		if (url->fragment != NULL) {
			meta_idx += util_strcpy(meta+meta_idx, "#",
					1025-meta_idx);
			util_strcpy(meta+meta_idx, url->fragment,
					1025-meta_idx);
		}
		srv_response_set_meta(meta);
		goto cleanup;
	}

	if (S_ISDIR(st.st_mode)) {
		/*
		 * If the requested resource is a directory, check for an .index
		 * file. If such a file is present, we either serve an index
		 * file or create a directory listing ourselves.
		 */
		len = strlen(path); /* + NUL */
		idx_path = calloc(len + 9, sizeof(char)); /* + /.indexNUL */
		strncpy(idx_path, path, len);
		strcpy(idx_path + len, "/.index");


		free(path);
		path = realpath(idx_path, NULL);
		if (path == NULL) {
			srv_response_set_status(51);
			srv_response_set_meta("Resource not available.");
			log_msg(LOGLEVEL_DEBUG, "Respond 51 because index file "
					"of directory is missing: %s",
					idx_path);
			goto cleanup;
		}

		len = strlen(path);
		if (strcmp(path + len - 7, "/.index") == 0) {
			path[len - 7] = 0x0;
			srv_response_dir_index(path, url);
			goto cleanup;
		}
		res = fopen(path, "rb");
	} else if (type == RESOURCETYPE_CGI) {
		/* TODO Set environment variables. */
		res = popen(path, "r");
	} else {
		res = fopen(path, "rb");
	}

	if (res == NULL) {
		srv_response_set_status(51);
		srv_response_set_meta("Resource not available.");
		log_msg(LOGLEVEL_ERROR, "Failed to open resource at: %s", path);
		goto cleanup;
	}

	if (type == RESOURCETYPE_PLAIN && !srv_resource_mime_from_file(path)
			&& !srv_response_meta_from_mimedb(path, configuration.mimedb)
			&& !srv_response_meta_from_ext(path)) {
		srv_response_set_status(51);
		srv_response_set_meta("Resource not available.");
		log_msg(LOGLEVEL_ERROR, "Failed to determine meta string for "
				"resource at: %s", path);
		fclose(res);
		goto cleanup;
	}
	srv_resource_update_mime_parameters(path);
	srv_response_set_data(res, type);

cleanup:
	free(path);
	free(enc_path);
	free(idx_path);
}