Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
native_http_request.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Parses HTTP requests from native connections into method, target, header, cookie, and body fields.
4 */
5
7
9#include "native_support.h"
10
11#include <stdlib.h>
12#include <string.h>
13#ifndef _WIN32
14#include <unistd.h>
15#endif
16
17#define PHAROS_HTTP_MAX_HEADER_BYTES (16U * 1024U)
18#define PHAROS_HTTP_MAX_REQUEST_LINE_BYTES (8U * 1024U)
19#define PHAROS_HTTP_MAX_HEADER_LINE_BYTES (4U * 1024U)
20#define PHAROS_HTTP_MAX_HEADER_COUNT 100U
21#define PHAROS_HTTP_MAX_BODY_BYTES (1U * 1024U * 1024U)
22#define PHAROS_HTTP_MAX_MULTIPART_BODY_BYTES (16U * 1024U * 1024U)
23
24#ifdef _WIN32
25#include <winsock2.h>
26#define native_http_request_strdup _strdup
27#else
28#include <strings.h>
29#include <sys/socket.h>
30#define native_http_request_strdup strdup
31#endif
32
34 if (request == NULL) {
35 return;
36 }
37 free(request->method);
38 free(request->target);
39 free(request->cookie);
40 free(request->content_type);
41 free(request->headers);
42 free(request->body);
44 memset(request, 0, sizeof(*request));
45}
46
47static int parse_content_length_header_native(const char *header_value, long *content_length) {
48 char *end = NULL;
49 long parsed_length;
50 if (header_value == NULL || content_length == NULL) {
51 return 400;
52 }
53 parsed_length = strtol(header_value, &end, 10);
54 if (end == header_value || parsed_length < 0) {
55 return 400;
56 }
57 while (end != NULL && *end != '\0') {
58 if (*end != ' ' && *end != '\t') {
59 return 400;
60 }
61 end++;
62 }
63 *content_length = parsed_length;
64 return 0;
65}
66
67static size_t native_http_body_limit(int is_multipart) {
69}
70
71static int contains_chunked_transfer_native(const char *header_value) {
72 const char *cursor;
73 if (header_value == NULL) {
74 return 0;
75 }
76 for (cursor = header_value; *cursor != '\0'; cursor++) {
77 if ((cursor[0] == 'c' || cursor[0] == 'C') &&
78 (cursor[1] == 'h' || cursor[1] == 'H') &&
79 (cursor[2] == 'u' || cursor[2] == 'U') &&
80 (cursor[3] == 'n' || cursor[3] == 'N') &&
81 (cursor[4] == 'k' || cursor[4] == 'K') &&
82 (cursor[5] == 'e' || cursor[5] == 'E') &&
83 (cursor[6] == 'd' || cursor[6] == 'D')) {
84 return 1;
85 }
86 }
87 return 0;
88}
89
90#ifndef _WIN32
91static int native_write_temp_body_chunk(int temp_fd, const char *data, size_t data_len) {
92 size_t written = 0;
93 if (temp_fd < 0 || (data == NULL && data_len > 0)) {
94 return 69;
95 }
96 while (written < data_len) {
97 ssize_t chunk_written = write(temp_fd, data + written, data_len - written);
98 if (chunk_written <= 0) {
99 return 69;
100 }
101 written += (size_t)chunk_written;
102 }
103 return 0;
104}
105
107 Buffer *buffer,
108 size_t body_offset,
109 long content_length,
110 NativeConnection *connection,
111 char **body_file_path_out
112) {
113 char temp_template[] = "/tmp/pharos-body-XXXXXX";
114 int temp_fd;
115 size_t initial_len;
116 size_t written = 0;
117 char chunk[4096];
118 if (buffer == NULL || connection == NULL || body_file_path_out == NULL || content_length < 0) {
119 return 69;
120 }
121 *body_file_path_out = NULL;
122 temp_fd = mkstemp(temp_template);
123 if (temp_fd < 0) {
124 return 69;
125 }
126 initial_len = buffer->len > body_offset ? buffer->len - body_offset : 0;
127 if ((long)initial_len > content_length) {
128 initial_len = (size_t)content_length;
129 }
130 if (initial_len > 0 && native_write_temp_body_chunk(temp_fd, buffer->data + body_offset, initial_len) != 0) {
131 close(temp_fd);
132 unlink(temp_template);
133 return 69;
134 }
135 written = initial_len;
136 while (written < (size_t)content_length) {
137 size_t remaining = (size_t)content_length - written;
138 size_t to_read = remaining < sizeof(chunk) ? remaining : sizeof(chunk);
139 int bytes_read = native_connection_read(connection, chunk, to_read);
140 if (bytes_read <= 0) {
141 close(temp_fd);
142 unlink(temp_template);
143 return 65;
144 }
145 if (native_write_temp_body_chunk(temp_fd, chunk, (size_t)bytes_read) != 0) {
146 close(temp_fd);
147 unlink(temp_template);
148 return 69;
149 }
150 written += (size_t)bytes_read;
151 }
152 if (close(temp_fd) != 0) {
153 unlink(temp_template);
154 return 69;
155 }
156 *body_file_path_out = native_http_request_strdup(temp_template);
157 if (*body_file_path_out == NULL) {
158 unlink(temp_template);
159 return 69;
160 }
161 return 0;
162}
163#else
165 Buffer *buffer,
166 size_t body_offset,
167 long content_length,
168 NativeConnection *connection,
169 char **body_file_path_out
170) {
171 (void)buffer;
172 (void)body_offset;
173 (void)content_length;
174 (void)connection;
175 if (body_file_path_out != NULL) {
176 *body_file_path_out = NULL;
177 }
178 return 78;
179}
180#endif
181
183 HttpRequest *request,
184 const char *header_name,
185 const char *header_value,
186 long *content_length,
187 int *chunked_transfer
188) {
189 static const struct {
190 const char *name;
191 int kind;
192 } header_map[] = {
193 { "Content-Length", 1 },
194 { "Content-Type", 2 },
195 { "Cookie", 3 },
196 { "Transfer-Encoding", 4 }
197 };
198 size_t index;
199 for (index = 0; index < sizeof(header_map) / sizeof(header_map[0]); index++) {
200 if (strcasecmp(header_name, header_map[index].name) != 0) {
201 continue;
202 }
203 switch (header_map[index].kind) {
204 case 1:
205 if (parse_content_length_header_native(header_value, content_length) != 0) {
206 *content_length = -1;
207 }
208 return;
209 case 2:
210 free(request->content_type);
211 request->content_type = native_http_request_strdup(header_value);
212 return;
213 case 3:
214 free(request->cookie);
215 request->cookie = native_http_request_strdup(header_value);
216 return;
217 case 4:
218 if (chunked_transfer != NULL && contains_chunked_transfer_native(header_value)) {
219 *chunked_transfer = 1;
220 }
221 return;
222 }
223 }
224}
225
227 Buffer buffer;
228 char chunk[4096];
229 int bytes_read;
230 char *header_end;
231 size_t header_len;
232 size_t body_offset;
233 long content_length = 0;
234 char *request_line_end;
235 char *space1;
236 char *space2;
237 char *headers_copy = NULL;
238 char *line;
239 int parse_code = 0;
240 int chunked_transfer = 0;
241 size_t header_count = 0;
242 size_t body_limit;
243 buffer_init(&buffer);
244 memset(request, 0, sizeof(*request));
245 while (1) {
246 bytes_read = native_connection_read(connection, chunk, sizeof(chunk));
247 if (bytes_read <= 0) {
248 parse_code = 65;
249 goto cleanup;
250 }
251 if (buffer_append(&buffer, chunk, (size_t)bytes_read) != 0) {
252 parse_code = 69;
253 goto cleanup;
254 }
255 header_end = strstr(buffer.data, "\r\n\r\n");
256 if (header_end == NULL) {
257 header_end = strstr(buffer.data, "\n\n");
258 }
259 if (header_end != NULL) {
260 break;
261 }
262 if (buffer.len > PHAROS_HTTP_MAX_HEADER_BYTES) {
263 parse_code = 431;
264 goto cleanup;
265 }
266 }
267 header_end = strstr(buffer.data, "\r\n\r\n");
268 if (header_end != NULL) {
269 header_len = (size_t)(header_end - buffer.data);
270 body_offset = header_len + 4;
271 } else {
272 header_end = strstr(buffer.data, "\n\n");
273 if (header_end == NULL) {
274 parse_code = 65;
275 goto cleanup;
276 }
277 header_len = (size_t)(header_end - buffer.data);
278 body_offset = header_len + 2;
279 }
280 if (header_len > PHAROS_HTTP_MAX_HEADER_BYTES) {
281 parse_code = 431;
282 goto cleanup;
283 }
284 headers_copy = dup_range(buffer.data, header_len);
285 if (headers_copy == NULL) {
286 parse_code = 69;
287 goto cleanup;
288 }
289 request_line_end = strstr(headers_copy, "\r\n");
290 if (request_line_end == NULL) {
291 request_line_end = strchr(headers_copy, '\n');
292 }
293 if (request_line_end == NULL) {
294 parse_code = 65;
295 goto cleanup;
296 }
297 *request_line_end = '\0';
298 if (strlen(headers_copy) > PHAROS_HTTP_MAX_REQUEST_LINE_BYTES) {
299 parse_code = 414;
300 goto cleanup;
301 }
302 space1 = strchr(headers_copy, ' ');
303 if (space1 == NULL) {
304 parse_code = 65;
305 goto cleanup;
306 }
307 *space1 = '\0';
308 request->method = native_http_request_strdup(headers_copy);
309 space2 = strchr(space1 + 1, ' ');
310 if (space2 == NULL) {
311 parse_code = 65;
312 goto cleanup;
313 }
314 *space2 = '\0';
315 request->target = native_http_request_strdup(space1 + 1);
316 request->content_type = native_http_request_strdup("application/x-www-form-urlencoded");
317 request->cookie = native_http_request_strdup("");
318 if (request->method == NULL || request->target == NULL || request->content_type == NULL || request->cookie == NULL) {
319 parse_code = 69;
320 goto cleanup;
321 }
322 {
323 Buffer header_lines;
324 buffer_init(&header_lines);
325 line = request_line_end + 1;
326 if (*line == '\n') {
327 line++;
328 }
329 while (line != NULL && *line != '\0') {
330 char *next = strstr(line, "\r\n");
331 if (next == NULL) {
332 next = strchr(line, '\n');
333 }
334 if (next != NULL) {
335 *next = '\0';
336 }
337 if (*line != '\0') {
338 char *colon;
339 header_count++;
340 if (header_count > PHAROS_HTTP_MAX_HEADER_COUNT || strlen(line) > PHAROS_HTTP_MAX_HEADER_LINE_BYTES) {
341 buffer_free(&header_lines);
342 parse_code = 431;
343 goto cleanup;
344 }
345 colon = strchr(line, ':');
346 if (colon == NULL) {
347 buffer_free(&header_lines);
348 parse_code = 400;
349 goto cleanup;
350 }
351 *colon = '\0';
352 trim_in_place(line);
353 trim_in_place(colon + 1);
354 apply_known_header_native(request, line, colon + 1, &content_length, &chunked_transfer);
355 if (content_length < 0) {
356 buffer_free(&header_lines);
357 parse_code = 400;
358 goto cleanup;
359 }
360 if (buffer_append(&header_lines, line, strlen(line)) != 0 ||
361 buffer_append(&header_lines, ":", 1) != 0 ||
362 buffer_append(&header_lines, colon + 1, strlen(colon + 1)) != 0 ||
363 buffer_append(&header_lines, "\n", 1) != 0) {
364 buffer_free(&header_lines);
365 parse_code = 69;
366 goto cleanup;
367 }
368 }
369 if (next == NULL) {
370 break;
371 }
372 line = next + 1;
373 if (*line == '\n') {
374 line++;
375 }
376 }
377 request->headers = header_lines.data;
378 }
379 if (chunked_transfer) {
380 parse_code = 501;
381 goto cleanup;
382 }
383 request->is_multipart = request->content_type != NULL && strstr(request->content_type, "multipart/form-data") != NULL;
384 body_limit = native_http_body_limit(request->is_multipart);
385 if ((unsigned long)content_length > (unsigned long)body_limit) {
386 parse_code = 413;
387 goto cleanup;
388 }
389 request->body_len = content_length > 0 ? (size_t)content_length : 0;
390 if (request->body_len == 0) {
391 parse_code = 0;
392 goto cleanup;
393 }
395 &buffer,
396 body_offset,
397 content_length,
398 connection,
399 &request->body_file_path
400 );
401 goto cleanup;
402 while ((long)(buffer.len - body_offset) < content_length) {
403 bytes_read = native_connection_read(connection, chunk, sizeof(chunk));
404 if (bytes_read <= 0) {
405 parse_code = 65;
406 goto cleanup;
407 }
408 if (buffer_append(&buffer, chunk, (size_t)bytes_read) != 0) {
409 parse_code = 69;
410 goto cleanup;
411 }
412 }
413 request->body = dup_range(buffer.data + body_offset, request->body_len);
414 if (request->body == NULL && request->body_len > 0) {
415 parse_code = 69;
416 goto cleanup;
417 }
418
419cleanup:
420 free(headers_copy);
421 buffer_free(&buffer);
422 if (parse_code != 0) {
424 }
425 return parse_code;
426}
int native_connection_read(NativeConnection *connection, void *buffer, size_t len)
Reads bytes from a native connection.
static int native_write_temp_body_chunk(int temp_fd, const char *data, size_t data_len)
#define PHAROS_HTTP_MAX_MULTIPART_BODY_BYTES
static int contains_chunked_transfer_native(const char *header_value)
#define PHAROS_HTTP_MAX_BODY_BYTES
static size_t native_http_body_limit(int is_multipart)
#define PHAROS_HTTP_MAX_REQUEST_LINE_BYTES
static int native_spool_request_body_to_temp_file(Buffer *buffer, size_t body_offset, long content_length, NativeConnection *connection, char **body_file_path_out)
void free_http_request_native(HttpRequest *request)
Releases heap-owned fields in a parsed HTTP request record.
static void apply_known_header_native(HttpRequest *request, const char *header_name, const char *header_value, long *content_length, int *chunked_transfer)
#define PHAROS_HTTP_MAX_HEADER_LINE_BYTES
#define PHAROS_HTTP_MAX_HEADER_BYTES
#define native_http_request_strdup
#define PHAROS_HTTP_MAX_HEADER_COUNT
int parse_http_request_native(NativeConnection *connection, HttpRequest *request)
Parses an HTTP request from a native connection into a request record.
static int parse_content_length_header_native(const char *header_value, long *content_length)
Declares the parsed HTTP request record and request parsing helpers.
void cleanup_native_temp_body_file(char **temp_body_path_io)
Removes a temporary request-body file and clears its stored path.
Declares temporary request-body file helpers.
void buffer_init(Buffer *buffer)
Initializes a growable buffer to an empty state.
void trim_in_place(char *text)
Trims leading and trailing ASCII whitespace from a string in place.
char * dup_range(const char *start, size_t len)
Duplicates a byte range into a newly allocated NUL-terminated string.
void buffer_free(Buffer *buffer)
Releases memory owned by a growable buffer.
int buffer_append(Buffer *buffer, const void *data, size_t len)
Appends bytes to a growable buffer and maintains a trailing NUL byte.
Declares shared low-level support helpers used across the Pharos native runtime.
Growable byte buffer used by parsing and string assembly helpers.
size_t len
char * data
Parsed HTTP request fields extracted from a native connection.
Accepted client connection and its resolved transport metadata.