<commit_before>#include "first.h"

#include "response.h"
#include "base.h"
#include "burl.h"
#include "fdevent.h"
#include "http_header.h"
#include "http_kv.h"
#include "log.h"
#include "stat_cache.h"
#include "chunk.h"

#include "configfile.h"

#include "plugin.h"

#include <sys/types.h>
#include <sys/stat.h>

#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int http_response_write_header(server *srv, connection *con) {
	buffer * const b = chunkqueue_prepend_buffer_open(con->write_queue);

	if (con->request.http_version == HTTP_VERSION_1_1) {
		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
	} else {
		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
	}
	http_status_append(b, con->http_status);

	/* disable keep-alive if requested */
	if (con->request_count > con->conf.max_keep_alive_requests || 0 == con->conf.max_keep_alive_idle) {
		con->keep_alive = 0;
	} else if (0 != con->request.content_length
		   && con->request.content_length != con->request_content_queue->bytes_in
		   && (con->mode == DIRECT || 0 == con->conf.stream_request_body)) {
		con->keep_alive = 0;
	} else {
		con->keep_alive_idle = con->conf.max_keep_alive_idle;
	}

	if ((con->response.htags & HTTP_HEADER_UPGRADE) && con->request.http_version == HTTP_VERSION_1_1) {
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade"));
	} else if (0 == con->keep_alive) {
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close"));
	} else if (con->request.http_version == HTTP_VERSION_1_0) {/*(&& con->keep_alive != 0)*/
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive"));
	}

	if (304 == con->http_status && (con->response.htags & HTTP_HEADER_CONTENT_ENCODING)) {
		http_header_response_unset(con, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"));
	}

	/* add all headers */
	for (size_t i = 0; i < con->response.headers->used; ++i) {
		const data_string * const ds = (data_string *)con->response.headers->data[i];

		if (buffer_string_is_empty(ds->value) || buffer_string_is_empty(ds->key)) continue;
		if ((ds->key->ptr[0] & 0xdf) == 'X') {
			if (0 == strncasecmp(ds->key->ptr, CONST_STR_LEN("X-Sendfile"))) continue;
			if (0 == strncasecmp(ds->key->ptr, CONST_STR_LEN("X-LIGHTTPD-"))) {
				if (0 == strncasecmp(ds->key->ptr+sizeof("X-LIGHTTPD-")-1, CONST_STR_LEN("KBytes-per-second"))) {
					/* "X-LIGHTTPD-KBytes-per-second" */
					long limit = strtol(ds->value->ptr, NULL, 10);
					if (limit > 0
					    && (limit < con->conf.kbytes_per_second
					        || 0 == con->conf.kbytes_per_second)) {
						if (limit > USHRT_MAX) limit= USHRT_MAX;
						con->conf.kbytes_per_second = limit;
					}
				}
				continue;
			}
		}

		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
		buffer_append_string_buffer(b, ds->key);
		buffer_append_string_len(b, CONST_STR_LEN(": "));
		buffer_append_string_buffer(b, ds->value);
	}

	if (!(con->response.htags & HTTP_HEADER_DATE)) {
		/* HTTP/1.1 requires a Date: header */
		buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: "));

		/* cache the generated timestamp */
		if (srv->cur_ts != srv->last_generated_date_ts) {
			buffer_clear(srv->ts_date_str);
			buffer_append_strftime(srv->ts_date_str, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
			srv->last_generated_date_ts = srv->cur_ts;
		}

		buffer_append_string_buffer(b, srv->ts_date_str);
	}

	if (!(con->response.htags & HTTP_HEADER_SERVER)) {
		if (!buffer_string_is_empty(con->conf.server_tag)) {
			buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: "));
			buffer_append_string_len(b, CONST_BUF_LEN(con->conf.server_tag));
		}
	}

	buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));

	con->bytes_header = buffer_string_length(b);

	if (con->conf.log_response_header) {
		log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b);
	}

	chunkqueue_prepend_buffer_commit(con->write_queue);
	return 0;
}

static handler_t http_response_physical_path_check(server *srv, connection *con) {
	stat_cache_entry *sce = NULL;

	if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
		/* file exists */
	} else {
		char *pathinfo = NULL;
		switch (errno) {
		case EACCES:
			con->http_status = 403;

			if (con->conf.log_request_handling) {
				log_error_write(srv, __FILE__, __LINE__,  "s",  "-- access denied");
				log_error_write(srv, __FILE__, __LINE__,  "sb", "Path         :", con->physical.path);
			}

			buffer_reset(con->physical.path);
			return HANDLER_FINISHED;
		case ENAMETOOLONG:
			/* file name to be read was too long. return 404 */
		case ENOENT:
			if (con->request.http_method == HTTP_METHOD_OPTIONS
			    && NULL != http_header_response_get(con, HTTP_HEADER_OTHER, CONST_STR_LEN("Allow"))) {
				con->http_status = 200;
				return HANDLER_FINISHED;
			}

			con->http_status = 404;

			if (con->conf.log_request_handling) {
				log_error_write(srv, __FILE__, __LINE__,  "s",  "-- file not found");
				log_error_write(srv, __FILE__, __LINE__,  "sb", "Path         :", con->physical.path);
			}

			buffer_reset(con->physical.path);
			return HANDLER_FINISHED;
		case ENOTDIR:
			/* PATH_INFO ! :) */
			break;
		default:
			/* we have no idea what happend. let's tell the user so. */
			con->http_status = 500;
			buffer_reset(con->physical.path);

			log_error_write(srv, __FILE__, __LINE__, "ssbsb",
					"file not found ... or so: ", strerror(errno),
					con->uri.path,
					"->", con->physical.path);

			return HANDLER_FINISHED;
		}

		/* not found, perhaps PATHINFO */

		{
			/*(might check at startup that s->document_root does not end in '/')*/
			size_t len = buffer_string_length(con->physical.basedir);
			if (len > 0 && '/' == con->physical.basedir->ptr[len-1]) --len;
			pathinfo = con->physical.path->ptr + len;
			if ('/' != *pathinfo) {
				pathinfo = NULL;
			}
			else if (pathinfo == con->physical.path->ptr) { /*(basedir is "/")*/
				pathinfo = strchr(pathinfo+1, '/');
			}
		}

		for (char *pprev = pathinfo; pathinfo; pprev = pathinfo, pathinfo = strchr(pathinfo+1, '/')) {
			stat_cache_entry *nsce = NULL;
			buffer_copy_string_len(srv->tmp_buf, con->physical.path->ptr, pathinfo - con->physical.path->ptr);
			if (HANDLER_ERROR == stat_cache_get_entry(srv, con, srv->tmp_buf, &nsce)) {
				pathinfo = pathinfo != pprev ? pprev : NULL;
				break;
			}<commit_msg>[core] emit trace using path before clearing path<commit_after>#include "first.h"

#include "response.h"
#include "base.h"
#include "burl.h"
#include "fdevent.h"
#include "http_header.h"
#include "http_kv.h"
#include "log.h"
#include "stat_cache.h"
#include "chunk.h"

#include "configfile.h"

#include "plugin.h"

#include <sys/types.h>
#include <sys/stat.h>

#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int http_response_write_header(server *srv, connection *con) {
	buffer * const b = chunkqueue_prepend_buffer_open(con->write_queue);

	if (con->request.http_version == HTTP_VERSION_1_1) {
		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
	} else {
		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
	}
	http_status_append(b, con->http_status);

	/* disable keep-alive if requested */
	if (con->request_count > con->conf.max_keep_alive_requests || 0 == con->conf.max_keep_alive_idle) {
		con->keep_alive = 0;
	} else if (0 != con->request.content_length
		   && con->request.content_length != con->request_content_queue->bytes_in
		   && (con->mode == DIRECT || 0 == con->conf.stream_request_body)) {
		con->keep_alive = 0;
	} else {
		con->keep_alive_idle = con->conf.max_keep_alive_idle;
	}

	if ((con->response.htags & HTTP_HEADER_UPGRADE) && con->request.http_version == HTTP_VERSION_1_1) {
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade"));
	} else if (0 == con->keep_alive) {
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close"));
	} else if (con->request.http_version == HTTP_VERSION_1_0) {/*(&& con->keep_alive != 0)*/
		http_header_response_set(con, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive"));
	}

	if (304 == con->http_status && (con->response.htags & HTTP_HEADER_CONTENT_ENCODING)) {
		http_header_response_unset(con, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"));
	}

	/* add all headers */
	for (size_t i = 0; i < con->response.headers->used; ++i) {
		const data_string * const ds = (data_string *)con->response.headers->data[i];

		if (buffer_string_is_empty(ds->value) || buffer_string_is_empty(ds->key)) continue;
		if ((ds->key->ptr[0] & 0xdf) == 'X') {
			if (0 == strncasecmp(ds->key->ptr, CONST_STR_LEN("X-Sendfile"))) continue;
			if (0 == strncasecmp(ds->key->ptr, CONST_STR_LEN("X-LIGHTTPD-"))) {
				if (0 == strncasecmp(ds->key->ptr+sizeof("X-LIGHTTPD-")-1, CONST_STR_LEN("KBytes-per-second"))) {
					/* "X-LIGHTTPD-KBytes-per-second" */
					long limit = strtol(ds->value->ptr, NULL, 10);
					if (limit > 0
					    && (limit < con->conf.kbytes_per_second
					        || 0 == con->conf.kbytes_per_second)) {
						if (limit > USHRT_MAX) limit= USHRT_MAX;
						con->conf.kbytes_per_second = limit;
					}
				}
				continue;
			}
		}

		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
		buffer_append_string_buffer(b, ds->key);
		buffer_append_string_len(b, CONST_STR_LEN(": "));
		buffer_append_string_buffer(b, ds->value);
	}

	if (!(con->response.htags & HTTP_HEADER_DATE)) {
		/* HTTP/1.1 requires a Date: header */
		buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: "));

		/* cache the generated timestamp */
		if (srv->cur_ts != srv->last_generated_date_ts) {
			buffer_clear(srv->ts_date_str);
			buffer_append_strftime(srv->ts_date_str, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
			srv->last_generated_date_ts = srv->cur_ts;
		}

		buffer_append_string_buffer(b, srv->ts_date_str);
	}

	if (!(con->response.htags & HTTP_HEADER_SERVER)) {
		if (!buffer_string_is_empty(con->conf.server_tag)) {
			buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: "));
			buffer_append_string_len(b, CONST_BUF_LEN(con->conf.server_tag));
		}
	}

	buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));

	con->bytes_header = buffer_string_length(b);

	if (con->conf.log_response_header) {
		log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b);
	}

	chunkqueue_prepend_buffer_commit(con->write_queue);
	return 0;
}

static handler_t http_response_physical_path_check(server *srv, connection *con) {
	stat_cache_entry *sce = NULL;

	if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
		/* file exists */
	} else {
		char *pathinfo = NULL;
		switch (errno) {
		case EACCES:
			con->http_status = 403;

			if (con->conf.log_request_handling) {
				log_error_write(srv, __FILE__, __LINE__,  "s",  "-- access denied");
				log_error_write(srv, __FILE__, __LINE__,  "sb", "Path         :", con->physical.path);
			}

			buffer_reset(con->physical.path);
			return HANDLER_FINISHED;
		case ENAMETOOLONG:
			/* file name to be read was too long. return 404 */
		case ENOENT:
			if (con->request.http_method == HTTP_METHOD_OPTIONS
			    && NULL != http_header_response_get(con, HTTP_HEADER_OTHER, CONST_STR_LEN("Allow"))) {
				con->http_status = 200;
				return HANDLER_FINISHED;
			}

			con->http_status = 404;

			if (con->conf.log_request_handling) {
				log_error_write(srv, __FILE__, __LINE__,  "s",  "-- file not found");
				log_error_write(srv, __FILE__, __LINE__,  "sb", "Path         :", con->physical.path);
			}

			buffer_reset(con->physical.path);
			return HANDLER_FINISHED;
		case ENOTDIR:
			/* PATH_INFO ! :) */
			break;
		default:
			/* we have no idea what happend. let's tell the user so. */
			log_error_write(srv, __FILE__, __LINE__, "ssbsb",
					"file not found ... or so: ", strerror(errno),
					con->uri.path,
					"->", con->physical.path);

			con->http_status = 500;
			buffer_reset(con->physical.path);

			return HANDLER_FINISHED;
		}

		/* not found, perhaps PATHINFO */

		{
			/*(might check at startup that s->document_root does not end in '/')*/
			size_t len = buffer_string_length(con->physical.basedir);
			if (len > 0 && '/' == con->physical.basedir->ptr[len-1]) --len;
			pathinfo = con->physical.path->ptr + len;
			if ('/' != *pathinfo) {
				pathinfo = NULL;
			}
			else if (pathinfo == con->physical.path->ptr) { /*(basedir is "/")*/
				pathinfo = strchr(pathinfo+1, '/');
			}
		}

		for (char *pprev = pathinfo; pathinfo; pprev = pathinfo, pathinfo = strchr(pathinfo+1, '/')) {
			stat_cache_entry *nsce = NULL;
			buffer_copy_string_len(srv->tmp_buf, con->physical.path->ptr, pathinfo - con->physical.path->ptr);
			if (HANDLER_ERROR == stat_cache_get_entry(srv, con, srv->tmp_buf, &nsce)) {
				pathinfo = pathinfo != pprev ? pprev : NULL;
				break;
			}
{'fraction_non_alphanumeric': 0.1657221626589064, 'fraction_numerical': 0.008117629039669168, 'mean_word_length': 4.190381558028617, 'pattern_counts': {'":': 2, '<': 21, '<?xml version=': 0, '>': 203, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2918544', 'n_tokens_mistral': 5564, 'n_tokens_neox': 4947, 'n_words': 859}