ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
http2.c
Go to the documentation of this file.
1#include "http2.h"
2
3#include <ascii-chat/common.h>
4#include <nghttp2/nghttp2.h>
5#include <string.h>
6
7#define HTTP2_MAX_TEXT 8192u
8#define HTTP2_MAX_HEADERS 64u
9#define HTTP2_MAX_CONCURRENT_STREAMS 128u
10
24
25typedef struct {
26 char *data;
27 size_t length;
28 size_t offset;
30
40
41static request_state_t *get_request(nghttp2_session *session, int32_t stream_id) {
42 return nghttp2_session_get_stream_user_data(session, stream_id);
43}
44
45static char *copy_text(const uint8_t *data, size_t length) {
46 if (length > HTTP2_MAX_TEXT)
47 return NULL;
48 char *copy = SAFE_MALLOC(length + 1, char *);
49 if (!copy)
50 return NULL;
51 memcpy(copy, data, length);
52 copy[length] = 0;
53 return copy;
54}
55
56static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) {
57 (void)user_data;
58 if (frame->hd.type != NGHTTP2_HEADERS)
59 return 0;
60 request_state_t *request = SAFE_CALLOC(1, sizeof(*request), request_state_t *);
61 if (!request)
62 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
63 request->stream_id = frame->hd.stream_id;
64 nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, request);
65 return 0;
66}
67
68static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t name_length,
69 const uint8_t *value, size_t value_length, uint8_t flags, void *user_data) {
70 (void)flags;
71 (void)user_data;
72 request_state_t *request = get_request(session, frame->hd.stream_id);
73 if (!request)
74 return NGHTTP2_ERR_CALLBACK_FAILURE;
75 if (name_length == sizeof(":method") - 1 && memcmp(name, ":method", sizeof(":method") - 1) == 0) {
76 SAFE_FREE(request->method);
77 request->method = copy_text(value, value_length);
78 } else if (name_length == sizeof(":path") - 1 && memcmp(name, ":path", sizeof(":path") - 1) == 0) {
79 SAFE_FREE(request->path);
80 request->path = copy_text(value, value_length);
81 } else if (request->header_count < HTTP2_MAX_HEADERS) {
82 char *header_name = copy_text(name, name_length);
83 char *header_value = copy_text(value, value_length);
84 if (!header_name || !header_value) {
85 SAFE_FREE(header_name);
86 SAFE_FREE(header_value);
87 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
88 }
89 request->header_names[request->header_count] = header_name;
90 request->header_values[request->header_count] = header_value;
91 request->headers[request->header_count] = (http2_header_t){header_name, header_value};
92 request->header_count++;
93 }
94 return (request->method || request->path) ? 0 : NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
95}
96
97static int on_data(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t length,
98 void *user_data) {
99 (void)flags;
100 (void)user_data;
101 request_state_t *request = get_request(session, stream_id);
102 http2_connection_t *connection = user_data;
103 if (!request || request->body_length > connection->max_body_length ||
104 length > connection->max_body_length - request->body_length)
105 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
106 size_t required = request->body_length + length;
107 if (request->body_capacity < required) {
108 size_t capacity = request->body_capacity ? request->body_capacity : 1024;
109 while (capacity < required) {
110 if (capacity > SIZE_MAX / 2) {
111 capacity = required;
112 break;
113 }
114 capacity *= 2;
115 }
116 char *body = SAFE_REALLOC(request->body, capacity, char *);
117 if (!body)
118 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
119 request->body = body;
120 request->body_capacity = capacity;
121 }
122 memcpy(request->body + request->body_length, data, length);
123 request->body_length += length;
124 return 0;
125}
126
127static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) {
128 http2_connection_t *connection = user_data;
129 if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) ||
130 (frame->hd.type != NGHTTP2_HEADERS && frame->hd.type != NGHTTP2_DATA))
131 return 0;
132 request_state_t *request = get_request(session, frame->hd.stream_id);
133 if (!request || request->dispatched || !request->method || !request->path)
134 return NGHTTP2_ERR_PROTO;
135 request->dispatched = true;
136 http2_request_view_t view = {.method = request->method,
137 .path = request->path,
138 .body = request->body,
139 .body_length = request->body_length,
140 .stream_id = request->stream_id,
141 .headers = request->headers,
142 .header_count = request->header_count};
143 char *query = strchr(request->path, '?');
144 if (query) {
145 *query = 0;
146 view.query = query + 1;
147 }
148 return connection->on_request(connection, &view, connection->user_data) == ASCIICHAT_OK
149 ? 0
150 : NGHTTP2_ERR_CALLBACK_FAILURE;
151}
152
153static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) {
154 (void)error_code;
155 (void)user_data;
156 request_state_t *request = get_request(session, stream_id);
157 if (!request)
158 return 0;
159 SAFE_FREE(request->method);
160 SAFE_FREE(request->path);
161 SAFE_FREE(request->body);
162 for (size_t i = 0; i < request->header_count; ++i) {
163 SAFE_FREE(request->header_names[i]);
164 SAFE_FREE(request->header_values[i]);
165 }
166 SAFE_FREE(request);
167 return 0;
168}
169
170static ssize_t read_response(nghttp2_session *session, int32_t stream_id, uint8_t *buffer, size_t length,
171 uint32_t *flags, nghttp2_data_source *source, void *user_data) {
172 (void)session;
173 (void)stream_id;
174 (void)user_data;
175 response_state_t *response = source->ptr;
176 size_t remaining = response->length - response->offset;
177 size_t copied = remaining < length ? remaining : length;
178 if (copied)
179 memcpy(buffer, response->data + response->offset, copied);
180 response->offset += copied;
181 if (response->offset == response->length) {
182 *flags |= NGHTTP2_DATA_FLAG_EOF;
183 SAFE_FREE(response->data);
184 SAFE_FREE(response);
185 }
186 return (ssize_t)copied;
187}
188
190 size_t max_request_body_length, size_t max_response_body_length) {
191 if (!out || !on_request || max_request_body_length == 0 || max_response_body_length == 0)
192 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing HTTP/2 request callback");
193 http2_connection_t *connection = SAFE_CALLOC(1, sizeof(*connection), http2_connection_t *);
194 nghttp2_session_callbacks *callbacks = NULL;
195 nghttp2_option *options = NULL;
196 if (nghttp2_session_callbacks_new(&callbacks) != 0 || nghttp2_option_new(&options) != 0) {
197 nghttp2_session_callbacks_del(callbacks);
198 nghttp2_option_del(options);
199 SAFE_FREE(connection);
200 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Cannot initialize HTTP/2 callbacks");
201 }
202 nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers);
203 nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header);
204 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data);
205 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv);
206 nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close);
207 if (nghttp2_session_server_new2(&connection->session, callbacks, connection, options) != 0) {
208 nghttp2_session_callbacks_del(callbacks);
209 nghttp2_option_del(options);
210 SAFE_FREE(connection);
211 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Cannot initialize HTTP/2 session");
212 }
213 nghttp2_session_callbacks_del(callbacks);
214 nghttp2_option_del(options);
215 nghttp2_settings_entry settings = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP2_MAX_CONCURRENT_STREAMS};
216 if (nghttp2_submit_settings(connection->session, NGHTTP2_FLAG_NONE, &settings, 1) != 0) {
217 nghttp2_session_del(connection->session);
218 SAFE_FREE(connection);
219 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Cannot configure HTTP/2 stream limit");
220 }
221 connection->on_request = on_request;
222 connection->user_data = user_data;
223 connection->max_body_length = max_request_body_length;
224 connection->max_response_body_length = max_response_body_length;
225 *out = connection;
226 return ASCIICHAT_OK;
227}
228
230 if (!connection || !*connection)
231 return;
232 nghttp2_session_del((*connection)->session);
233 SAFE_FREE(*connection);
234 *connection = NULL;
235}
236
237asciichat_error_t http2_connection_receive(http2_connection_t *connection, const void *data, size_t length) {
238 if (!connection || (!data && length))
239 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP/2 input");
240 if (nghttp2_session_mem_recv(connection->session, data, length) < 0)
241 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid HTTP/2 frame sequence");
242 return ASCIICHAT_OK;
243}
244
246 if (!connection || !data || !length)
247 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP/2 output request");
248 ssize_t output_length = nghttp2_session_mem_send(connection->session, &connection->output);
249 if (output_length < 0)
250 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Cannot serialize HTTP/2 output");
251 connection->output_length = (size_t)output_length;
252 *data = connection->output;
253 *length = connection->output_length;
254 return ASCIICHAT_OK;
255}
256
257asciichat_error_t http2_connection_submit_response(http2_connection_t *connection, int32_t stream_id, unsigned status,
258 const http_header_t *headers, size_t header_count, const void *body,
259 size_t body_length) {
260 if (!connection || status < 100 || status > 599 || body_length > connection->max_response_body_length ||
261 (!body && body_length))
262 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP/2 response");
263 if (header_count > HTTP2_MAX_HEADERS || (header_count && !headers))
264 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP/2 response headers");
265 char *status_text = SAFE_MALLOC(4, char *);
266 if (!status_text)
267 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate HTTP/2 response status");
268 safe_snprintf(status_text, 4, "%u", status);
269 response_state_t *response = SAFE_CALLOC(1, sizeof(*response), response_state_t *);
270 if (!response) {
271 SAFE_FREE(status_text);
272 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate HTTP/2 response state");
273 }
274 response->length = body_length;
275 if (body_length) {
276 response->data = SAFE_MALLOC(body_length, char *);
277 if (!response->data) {
278 SAFE_FREE(response);
279 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate HTTP/2 response body");
280 }
281 memcpy(response->data, body, body_length);
282 }
283 nghttp2_nv *fields = SAFE_CALLOC(header_count + 1, sizeof(*fields), nghttp2_nv *);
284 if (!fields) {
285 SAFE_FREE(status_text);
286 SAFE_FREE(response->data);
287 SAFE_FREE(response);
288 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate HTTP/2 response headers");
289 }
290 fields[0] = (nghttp2_nv){(uint8_t *)":status", (uint8_t *)status_text, 7, strlen(status_text), NGHTTP2_NV_FLAG_NONE};
291 for (size_t i = 0; i < header_count; ++i) {
292 if (!headers[i].name || !headers[i].value) {
293 SAFE_FREE(fields);
294 SAFE_FREE(status_text);
295 SAFE_FREE(response->data);
296 SAFE_FREE(response);
297 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP/2 response header");
298 }
299 fields[i + 1] = (nghttp2_nv){(uint8_t *)headers[i].name, (uint8_t *)headers[i].value, strlen(headers[i].name),
300 strlen(headers[i].value), NGHTTP2_NV_FLAG_NONE};
301 }
302 nghttp2_data_provider provider = {.source = {.ptr = response}, .read_callback = read_response};
303 int result =
304 nghttp2_submit_response(connection->session, stream_id, fields, header_count + 1, body_length ? &provider : NULL);
305 SAFE_FREE(fields);
306 SAFE_FREE(status_text);
307 if (!body_length)
308 SAFE_FREE(response);
309 if (result != 0) {
310 if (body_length) {
311 SAFE_FREE(response->data);
312 SAFE_FREE(response);
313 }
314 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Cannot submit HTTP/2 response");
315 }
316 return ASCIICHAT_OK;
317}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
audio_visualization_source_t source
Definition fft.c:24
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:285
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned char uint8_t
Definition common.h:60
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
void http2_connection_destroy(http2_connection_t **connection)
Definition http2.c:229
asciichat_error_t http2_connection_receive(http2_connection_t *connection, const void *data, size_t length)
Definition http2.c:237
asciichat_error_t http2_connection_create(http2_connection_t **out, http2_request_fn on_request, void *user_data, size_t max_request_body_length, size_t max_response_body_length)
Definition http2.c:189
#define HTTP2_MAX_CONCURRENT_STREAMS
Definition http2.c:9
asciichat_error_t http2_connection_submit_response(http2_connection_t *connection, int32_t stream_id, unsigned status, const http_header_t *headers, size_t header_count, const void *body, size_t body_length)
Definition http2.c:257
#define HTTP2_MAX_HEADERS
Definition http2.c:8
asciichat_error_t http2_connection_next_output(http2_connection_t *connection, const uint8_t **data, size_t *length)
Definition http2.c:245
#define HTTP2_MAX_TEXT
Definition http2.c:7
struct http2_header http2_header_t
asciichat_error_t(* http2_request_fn)(http2_connection_t *connection, const http2_request_view_t *request, void *user_data)
Definition http2.h:23
struct http2_connection http2_connection_t
Definition http2.h:8
static bool size_t gotel_query_t * out
Definition queries.h:8
🔤 String Manipulation and Shell Escaping Utilities
size_t max_response_body_length
Definition http2.c:36
nghttp2_session * session
Definition http2.c:32
size_t output_length
Definition http2.c:38
size_t max_body_length
Definition http2.c:35
http2_request_fn on_request
Definition http2.c:33
void * user_data
Definition http2.c:34
const uint8_t * output
Definition http2.c:37
const char * query
Definition http2.h:12
size_t body_length
Definition http2.c:16
http2_header_t headers[64u]
Definition http2.c:18
char * path
Definition http2.c:14
char * header_values[64u]
Definition http2.c:20
bool dispatched
Definition http2.c:22
size_t header_count
Definition http2.c:21
size_t body_capacity
Definition http2.c:17
char * body
Definition http2.c:15
char * header_names[64u]
Definition http2.c:19
char * method
Definition http2.c:13
int32_t stream_id
Definition http2.c:12
char * data
Definition http2.c:26
size_t length
Definition http2.c:27
size_t offset
Definition http2.c:28