summaryrefslogtreecommitdiffhomepage
path: root/response.c
blob: 4f70b0dc40c10b15f5d7cad1d266331806ffe119 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include "response.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <lua.h>

/// Sends a response based on the current top value on the stack.
/// \param L Lua state to send response for
/// \param fd File descriptor of a socket to write to
/// \return Bytes written or -1 on error
/// \see write(2)
// TODO: Consider splitting response_send into smaller functions.
int response_send(lua_State * L, const int fd)
{
	static const char * error_response =
		"HTTP/1.1 500\r\n"
		"Connection: close\r\n"
		"\r\n";

	if (0 == lua_istable(L, -1) || 0 == lua_checkstack(L, 5))
	{
		lua_pop(L, 1);
		return write(fd, error_response, strlen(error_response));
	}

	int bytes_total = 2048 * sizeof(char);
	int bytes_used = 0;
	char * buffer = malloc(bytes_total);
	char * new_buffer = NULL;

	lua_pushliteral(L, "status");
	lua_gettable(L, -2);
	const lua_Integer status = lua_tointeger(L, -1);
	lua_pop(L, 1);

	if (0 == status || NULL == buffer)
	{
		free(buffer);
		lua_pop(L, 1);
		return write(fd, error_response, strlen(error_response));
	}

	bytes_used = snprintf(buffer, bytes_total, "HTTP/1.1 %d\r\n\r\n", (int) status) - 2;

	lua_pushliteral(L, "headers");
	lua_gettable(L, -2);

	if (0 == lua_istable(L, -1))
	{
		const int result = write(fd, buffer, bytes_used + 2);
		free(buffer);
		lua_pop(L, 2);
		return result;
	}

	static const char * header_pattern = "%s: %s\r\n";

	lua_pushnil(L);
	while (0 != lua_next(L, -2) && 0 != lua_isstring(L, -2))
	{
		const char * key = lua_tostring(L, -2);
		const char * value = lua_tostring(L, -1); // TODO: Check the type of the header's value?
		const int bytes_left = bytes_total - bytes_used;
		int new_bytes = snprintf(buffer + bytes_used, bytes_left, header_pattern, key, value);
		while (bytes_left <= new_bytes)
		{
			bytes_total += 2048;
			if (65536 < bytes_total)
			{
				free(buffer);
				lua_pop(L, 4);
				return write(fd, error_response, strlen(error_response));
			}
			new_buffer = realloc(buffer, bytes_total); // TODO: Realloc and error handling involves duplication.
			if (NULL == new_buffer)
			{
				free(buffer);
				lua_pop(L, 4);
				return write(fd, error_response, strlen(error_response));
			}
			buffer = new_buffer;
			new_bytes = snprintf(buffer + bytes_used, bytes_left, header_pattern, key, value);
		}
		bytes_used += new_bytes;
		lua_pop(L, 1);
	}

	if (bytes_total < bytes_used + 2)
	{
		bytes_total += 2;
		new_buffer = realloc(buffer, bytes_total);
		if (NULL == new_buffer)
		{
			lua_pop(L, 2);
			return write(fd, error_response, strlen(error_response));
		}
		buffer = new_buffer;
	}

	lua_pop(L, 1);
	buffer[bytes_used] = '\r';
	buffer[bytes_used + 1] = '\n';
	bytes_used += 2;

	lua_pushliteral(L, "data");
	lua_gettable(L, -2);
	size_t data_length = 0;
	const char * data = lua_tolstring(L, -1, &data_length);

	if (NULL != data && 0 < data_length)
	{
		// TODO: There are quite a few parts which could be moved out of this function.
		if (bytes_total < bytes_used + (int) data_length)
		{
			bytes_total += data_length;
			new_buffer = realloc(buffer, bytes_total);
			if (NULL == new_buffer)
			{
				free(buffer);
				lua_pop(L, 2);
				return write(fd, error_response, strlen(error_response));
			}
			buffer = new_buffer;
		}

		memcpy(buffer + bytes_used, data, data_length);
		bytes_used += data_length;
	}

	int result = write(fd, buffer, bytes_used);
	free(buffer); // TODO: Don't free the buffer if EAGAIN or EWOULDBLOCK, so it can be reused.
	lua_pop(L, 2);

	return result;
}