iodine 0.2.17 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +36 -3
  4. data/bin/config.ru +23 -2
  5. data/bin/http-hello +1 -1
  6. data/bin/ws-shootout +5 -0
  7. data/ext/iodine/defer.c +468 -0
  8. data/ext/iodine/defer.h +105 -0
  9. data/ext/iodine/evio.c +263 -0
  10. data/ext/iodine/evio.h +133 -0
  11. data/ext/iodine/extconf.rb +2 -1
  12. data/ext/iodine/facil.c +958 -0
  13. data/ext/iodine/facil.h +423 -0
  14. data/ext/iodine/http.c +90 -0
  15. data/ext/iodine/http.h +50 -12
  16. data/ext/iodine/http1.c +200 -267
  17. data/ext/iodine/http1.h +17 -26
  18. data/ext/iodine/http1_request.c +81 -0
  19. data/ext/iodine/http1_request.h +58 -0
  20. data/ext/iodine/http1_response.c +403 -0
  21. data/ext/iodine/http1_response.h +90 -0
  22. data/ext/iodine/http1_simple_parser.c +124 -108
  23. data/ext/iodine/http1_simple_parser.h +8 -3
  24. data/ext/iodine/http_request.c +104 -0
  25. data/ext/iodine/http_request.h +58 -102
  26. data/ext/iodine/http_response.c +212 -208
  27. data/ext/iodine/http_response.h +89 -252
  28. data/ext/iodine/iodine_core.c +57 -46
  29. data/ext/iodine/iodine_core.h +3 -1
  30. data/ext/iodine/iodine_http.c +105 -81
  31. data/ext/iodine/iodine_websocket.c +17 -13
  32. data/ext/iodine/iodine_websocket.h +1 -0
  33. data/ext/iodine/rb-call.c +9 -7
  34. data/ext/iodine/{rb-libasync.h → rb-defer.c} +57 -49
  35. data/ext/iodine/rb-rack-io.c +12 -6
  36. data/ext/iodine/rb-rack-io.h +1 -1
  37. data/ext/iodine/rb-registry.c +5 -2
  38. data/ext/iodine/sock.c +1159 -0
  39. data/ext/iodine/{libsock.h → sock.h} +138 -142
  40. data/ext/iodine/spnlock.inc +77 -0
  41. data/ext/iodine/websockets.c +101 -112
  42. data/ext/iodine/websockets.h +38 -19
  43. data/iodine.gemspec +3 -3
  44. data/lib/iodine/version.rb +1 -1
  45. data/lib/rack/handler/iodine.rb +6 -6
  46. metadata +23 -19
  47. data/ext/iodine/http_response_http1.h +0 -382
  48. data/ext/iodine/libasync.c +0 -570
  49. data/ext/iodine/libasync.h +0 -122
  50. data/ext/iodine/libreact.c +0 -350
  51. data/ext/iodine/libreact.h +0 -244
  52. data/ext/iodine/libserver.c +0 -957
  53. data/ext/iodine/libserver.h +0 -481
  54. data/ext/iodine/libsock.c +0 -1025
  55. data/ext/iodine/spnlock.h +0 -243
@@ -6,7 +6,9 @@ Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
  #ifndef HTTP1_SIMPLE_PARSER_H
8
8
  #define HTTP1_SIMPLE_PARSER_H
9
+ #include "http1.h"
9
10
  #include "http_request.h"
11
+ #include <stdio.h>
10
12
 
11
13
  #ifndef HTTP_HEADERS_LOWERCASE
12
14
  /** when defined, HTTP headers will be converted to lowercase and header
@@ -35,9 +37,12 @@ attempt, only the `len` argument is expected to grow.
35
37
 
36
38
  The buffer should be kept intact for the life of the request object, as the
37
39
  HTTP/1.1 parser does NOT copy any data.
40
+
41
+ The `on_header_found` allows the caller to save any header locations and data.
38
42
  */
39
- ssize_t http1_parse_request_headers(void *buffer, size_t len,
40
- http_request_s *request);
43
+ ssize_t http1_parse_request_headers(
44
+ void *buffer, size_t len, http_request_s *request,
45
+ void (*on_header_found)(http_request_s *request, http_header_s *header));
41
46
 
42
47
  /**
43
48
  Parses HTTP request body content (if any).
@@ -57,7 +62,7 @@ ssize_t http1_parse_request_body(void *buffer, size_t len,
57
62
  http_request_s *request);
58
63
 
59
64
  #if defined(DEBUG) && DEBUG == 1
60
- void http_parser_test(void);
65
+ void http1_parser_test(void);
61
66
  #endif
62
67
 
63
68
  #endif
@@ -0,0 +1,104 @@
1
+ /*
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "http.h"
8
+ #include "http1_request.h"
9
+
10
+ /* *****************************************************************************
11
+ Unsupported function placeholders
12
+ ***************************************************************************** */
13
+
14
+ static http_request_s *fail_create(void) { return NULL; }
15
+
16
+ static void fail_destroy(http_request_s *req) {
17
+ (void)req;
18
+ fprintf(stderr, "ERROR: possible memory leak - request to be freed has "
19
+ "unsupported version.\n");
20
+ exit(9);
21
+ }
22
+
23
+ static http_request_s *fail_dup(http_request_s *req) {
24
+ (void)req;
25
+ return NULL;
26
+ }
27
+
28
+ static http_header_s http_request_header_find_fail(http_request_s *request,
29
+ const char *header,
30
+ size_t header_len) {
31
+ (void)request;
32
+ (void)header;
33
+ (void)header_len;
34
+ return (http_header_s){.name = NULL};
35
+ }
36
+ static http_header_s http_request_header_seek_fail(http_request_s *request) {
37
+ (void)request;
38
+ return (http_header_s){.name = NULL};
39
+ }
40
+
41
+ /* *****************************************************************************
42
+ Initialization
43
+ ***************************************************************************** */
44
+
45
+ /** Creates / allocates a protocol version's request object. */
46
+ http_request_s *http_request_create(enum HTTP_VERSION ver) {
47
+ static http_request_s *(*const vtable[2])(void) = {
48
+ http1_request_create /* HTTP_V1 */, fail_create /* HTTP_V2 */};
49
+ return vtable[ver]();
50
+ }
51
+ /** Destroys the request object. */
52
+ void http_request_destroy(http_request_s *request) {
53
+ static void (*const vtable[2])(http_request_s *) = {
54
+ http1_request_destroy /* HTTP_V1 */, fail_destroy /* HTTP_V2 */};
55
+ vtable[request->http_version](request);
56
+ }
57
+ /** Recycle's the request object, clearing it's data. */
58
+ void http_request_clear(http_request_s *request) {
59
+ static void (*const vtable[2])(http_request_s *) = {
60
+ http1_request_clear /* HTTP_V1 */, fail_destroy /* HTTP_V2 */};
61
+ vtable[request->http_version](request);
62
+ }
63
+
64
+ /** Duplicates a request object. */
65
+ http_request_s *http_request_dup(http_request_s *request) {
66
+ static http_request_s *(*const vtable[2])(http_request_s *) = {
67
+ http1_request_dup /* HTTP_V1 */, fail_dup /* HTTP_V2 */};
68
+ return vtable[request->http_version](request);
69
+ }
70
+
71
+ /* *****************************************************************************
72
+ Header Access
73
+ ***************************************************************************** */
74
+
75
+ /** searches for a header in the request's data store, returning a `header_s`
76
+ * structure with all it's data.*/
77
+ http_header_s http_request_header_find(http_request_s *request,
78
+ const char *header, size_t header_len) {
79
+ static http_header_s (*const vtable[2])(http_request_s *, const char *,
80
+ size_t) = {
81
+ http1_request_header_find /* HTTP_V1 */,
82
+ http_request_header_find_fail /* HTTP_V2 */};
83
+ return vtable[request->http_version](request, header, header_len);
84
+ }
85
+ /** Starts itterating the header list, returning the first header. Header
86
+ * itteration is NOT thread-safe. */
87
+ http_header_s http_request_header_first(http_request_s *request) {
88
+ static http_header_s (*const vtable[2])(http_request_s *) = {
89
+ http1_request_header_first /* HTTP_V1 */,
90
+ http_request_header_seek_fail /* HTTP_V2 */};
91
+ return vtable[request->http_version](request);
92
+ }
93
+ /**
94
+ * Continues itterating the header list.
95
+ *
96
+ * Returns NULL header data if at end of list (header.name == NULL);
97
+ *
98
+ * Header itteration is NOT thread-safe. */
99
+ http_header_s http_request_header_next(http_request_s *request) {
100
+ static http_header_s (*const vtable[2])(http_request_s *) = {
101
+ http1_request_header_next /* HTTP_V1 */,
102
+ http_request_header_seek_fail /* HTTP_V2 */};
103
+ return vtable[request->http_version](request);
104
+ }
@@ -4,40 +4,25 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
- #ifndef HTTP_REQUEST_H
8
- #define HTTP_REQUEST_H
9
-
10
- #ifndef _GNU_SOURCE
11
- #define _GNU_SOURCE
12
- #endif
13
-
14
- #include <stdint.h>
15
- #include <stdio.h>
16
- #include <stdlib.h>
17
- #include <string.h>
18
- #include <strings.h>
19
- #include <unistd.h>
20
-
21
- #ifndef UNUSED_FUNC
22
- #define UNUSED_FUNC __attribute__((unused))
23
- #endif
24
-
25
- typedef struct {
26
- const char *name;
27
- const char *value;
28
- uint16_t name_length;
29
- uint16_t value_length;
30
- } http_headers_s;
31
-
32
- typedef struct {
7
+ #ifndef H_HTTP_REQUEST_H
8
+ #define H_HTTP_REQUEST_H
9
+ typedef struct http_request_s http_request_s;
10
+ #include "http.h"
11
+
12
+ struct http_request_s {
13
+ /** the HTTP version, also controlls the `http_request_s` flavor. */
14
+ enum HTTP_VERSION http_version;
15
+ /** the HTTP connection identifier. */
16
+ intptr_t fd;
17
+ /** this is an opaque pointer, intended for request pooling / chaining */
18
+ void *udata;
33
19
  /** points to the HTTP method name. */
34
20
  const char *method;
35
21
  /** The portion of the request URL that comes before the '?', if any. */
36
22
  const char *path;
37
- /** The string length of the path (editing the path requires update). */
38
23
  /** The portion of the request URL that follows the ?, if any. */
39
24
  const char *query;
40
- /** Points to a version string. */
25
+ /** Points to a version string, if any. */
41
26
  const char *version;
42
27
  /** Points to the body's host header value (a required header). */
43
28
  const char *host;
@@ -47,88 +32,59 @@ typedef struct {
47
32
  const char *upgrade;
48
33
  /** points to the Connection header, if any. */
49
34
  const char *connection;
50
-
51
35
  /** the body's content's length, in bytes (can be 0). */
52
36
  size_t content_length;
53
-
37
+ /** Points the body of the request, if the body exists and is stored in
38
+ * memory. Otherwise, NULL. */
39
+ const char *body_str;
40
+ /** points a tmpfile file descriptor containing the body of the request (if
41
+ * not in memory). */
42
+ int body_file;
54
43
  /* string lengths */
55
-
56
44
  uint16_t method_len;
57
45
  uint16_t path_len;
58
46
  uint16_t query_len;
59
47
  uint16_t host_len;
60
48
  uint16_t content_type_len;
61
49
  uint16_t upgrade_len;
62
-
63
- /** `version_len` is signed, to allow negative values (SPDY/HTTP2 etc). */
64
- int16_t version_len;
65
-
66
- /**
67
- Points the body of the request, if the body exists and is stored in memory.
68
- Otherwise, NULL. */
69
- const char *body_str;
70
- /** points a tmpfile file descriptor containing the body of the request. */
71
- int body_file;
72
- /** semi-private information. */
73
- struct {
74
- /**
75
- When pooling request objects, this points to the next object.
76
- In other times it may contain arbitrary data that can be used by the parser
77
- or implementation.
78
- */
79
- void *next;
80
- /**
81
- Implementation specific. This, conceptually, holds information about the
82
- "owner" of this request. */
83
- void *owner;
84
- /**
85
- Implementation specific. This, conceptually, holds the connection that
86
- "owns" this request, or an implementation identifier. */
87
- intptr_t fd;
88
- /** the current header position, for API or parser states. */
89
- uint16_t headers_pos;
90
- /** the maximum number of header space availble. */
91
- uint16_t max_headers;
92
- } metadata;
93
-
50
+ uint16_t version_len;
94
51
  uint16_t connection_len;
95
52
  uint16_t headers_count;
96
-
97
- http_headers_s headers[];
98
- } http_request_s;
99
-
100
- UNUSED_FUNC static inline void http_request_clear(http_request_s *request) {
101
- if (request->body_file > 0) /* assumes no tempfile with fd 0 */
102
- close(request->body_file);
103
- *request = (http_request_s){
104
- .metadata.owner = request->metadata.owner,
105
- .metadata.fd = request->metadata.fd,
106
- .metadata.max_headers = request->metadata.max_headers,
107
- };
108
- }
109
-
110
- /** searches for a header in the header array, both reaturnning it's value and
111
- * setting it's position in the `request->metadata.header_pos` variable.*/
112
- UNUSED_FUNC static inline const char *
113
- http_request_find_header(http_request_s *request, const char *header,
114
- size_t header_len) {
115
- if (header == NULL || request == NULL)
116
- return NULL;
117
- if (header_len == 0)
118
- header_len = strlen(header);
119
- request->metadata.headers_pos = 0;
120
- while (request->metadata.headers_pos < request->headers_count) {
121
- if (header_len ==
122
- request->headers[request->metadata.headers_pos].name_length &&
123
- strncasecmp(request->headers[request->metadata.headers_pos].name,
124
- header, header_len) == 0)
125
- return request->headers[request->metadata.headers_pos].value;
126
- request->metadata.headers_pos += 1;
127
- }
128
- return NULL;
129
- }
130
-
131
- #define HTTP_REQUEST_SIZE(header_count) \
132
- (sizeof(http_request_s) + ((header_count) * sizeof(http_headers_s)))
133
-
134
- #endif
53
+ };
54
+
55
+ /* *****************************************************************************
56
+ Initialization
57
+ ***************************************************************************** */
58
+
59
+ /** Creates / allocates a protocol version's request object. */
60
+ http_request_s *http_request_create(enum HTTP_VERSION);
61
+ /** Destroys the request object. */
62
+ void http_request_destroy(http_request_s *);
63
+ /** Recycle's the request object, clearing it's data. */
64
+ void http_request_clear(http_request_s *request);
65
+ /** Duplicates a request object. */
66
+ http_request_s *http_request_dup(http_request_s *);
67
+
68
+ /* *****************************************************************************
69
+ Header Access
70
+ ***************************************************************************** */
71
+
72
+ /** searches for a header in the request's data store, returning a `header_s`
73
+ * structure with all it's data.
74
+ *
75
+ * This doesn't effect header iteration.
76
+ */
77
+ http_header_s http_request_header_find(http_request_s *request,
78
+ const char *header, size_t header_len);
79
+ /** Starts iterating the header list, returning the first header. Header
80
+ * iteration is NOT thread-safe. */
81
+ http_header_s http_request_header_first(http_request_s *request);
82
+ /**
83
+ * Continues iterating the header list.
84
+ *
85
+ * Returns NULL header data if at end of list (header.name == NULL);
86
+ *
87
+ * Header itteration is NOT thread-safe. */
88
+ http_header_s http_request_header_next(http_request_s *request);
89
+
90
+ #endif /* H_HTTP_REQUEST_H */
@@ -1,152 +1,121 @@
1
- /*
2
- Copyright: Boaz segev, 2016-2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
- #ifndef _GNU_SOURCE
8
- #define _GNU_SOURCE
9
- #endif
10
-
11
- // clang-format off
12
- #include "http_response_http1.h"
13
- // clang-format on
14
-
15
1
  #include "base64.h"
16
2
  #include "http.h"
3
+ #include "http1_response.h"
17
4
  #include "siphash.h"
5
+
18
6
  #include <arpa/inet.h>
7
+ #include <errno.h>
19
8
  #include <fcntl.h>
20
- #include <netinet/in.h>
21
- #include <netinet/ip.h>
9
+ #include <limits.h>
10
+ #include <netdb.h>
11
+ #include <stdio.h>
12
+ #include <string.h>
13
+ #include <strings.h>
22
14
  #include <sys/resource.h>
23
15
  #include <sys/socket.h>
24
16
  #include <sys/stat.h>
17
+ #include <sys/time.h>
25
18
  #include <sys/types.h>
26
19
  #include <time.h>
27
20
  #include <unistd.h>
28
21
 
22
+ #ifndef PATH_MAX
23
+ #define PATH_MAX 4096
24
+ #endif
29
25
  /* *****************************************************************************
30
- Helpers
26
+ Fallbacks
31
27
  ***************************************************************************** */
32
28
 
33
- #define forward_func(response, func, ...) \
34
- if ((response)->metadata.version == 1) { \
35
- return h1p_##func(__VA_ARGS__); \
36
- } else \
37
- return -1;
38
- #define perform_func(response, func, ...) \
39
- if ((response)->metadata.version == 1) { \
40
- h1p_##func(__VA_ARGS__); \
41
- }
42
-
43
- // static char* MONTH_NAMES[] = {"Jan ", "Feb ", "Mar ", "Apr ", "May ", "Jun ",
44
- // "Jul ", "Aug ", "Sep ", "Oct ", "Nov ", "Dec
45
- // "};
46
- //
47
- #define is_num(c) ((c) >= '0' && (c) <= '9')
48
- #define num_val(c) ((c)-48)
29
+ static http_response_s *fallback_http_response_create(http_request_s *request) {
30
+ (void)request;
31
+ return NULL;
32
+ }
33
+ static void fallback_http_response_dest(http_response_s *res) {
34
+ (void)res;
35
+ return;
36
+ }
49
37
 
50
38
  /* *****************************************************************************
51
- API implementation
39
+ Initialization
52
40
  ***************************************************************************** */
53
41
 
54
- /**
55
- Initializes a response object with the request object. This function assumes the
56
- response object memory is garbage and might have been stack-allocated.
57
-
58
- Notice that the `http_request_s` pointer must point at a valid request object
59
- and that the request object must remain valid until the response had been
60
- completed.
61
-
62
- Hangs on failuer (waits for available resources).
63
- */
64
- http_response_s http_response_init(http_request_s *request) {
65
- protocol_s *http = request->metadata.owner;
66
- time_t date = server_last_tick();
67
- return (http_response_s){
68
- .metadata.request = request,
69
- .metadata.fd = request->metadata.fd,
70
- .metadata.packet = sock_checkout_packet(),
71
- .status = 200,
72
- .date = date,
73
- .last_modified = date,
74
- .metadata.version = (http->service == HTTP1 ? 1 : 0),
75
- .metadata.should_close =
76
- (request && request->connection &&
77
- request->connection_len ==
78
- 5), // don't check header value, length is unique enough.
79
- };
42
+ /** Creates / allocates a protocol version's response object. */
43
+ http_response_s *http_response_create(http_request_s *request) {
44
+ static http_response_s *(*const vtable[2])(http_request_s *) = {
45
+ http1_response_create /* HTTP_V1 */,
46
+ fallback_http_response_create /* HTTP_V2 */};
47
+ return vtable[request->http_version](request);
80
48
  }
81
- /**
82
- Releases any resources held by the response object (doesn't release the response
83
- object itself, which might have been allocated on the stack).
84
-
85
- This function assumes the response object might have been stack-allocated.
86
- */
49
+ /** Destroys the response object. No data is sent.*/
87
50
  void http_response_destroy(http_response_s *response) {
88
- if (response->metadata.packet) {
89
- sock_free_packet(response->metadata.packet);
90
- response->metadata.packet = NULL;
91
- }
51
+ if (!response)
52
+ return;
53
+ static void (*const vtable[2])(http_response_s *) = {
54
+ http1_response_destroy /* HTTP_V1 */,
55
+ fallback_http_response_dest /* HTTP_V2 */};
56
+ vtable[response->http_version](response);
92
57
  }
93
- /**
94
- Writes a header to the response. This function writes only the requested
95
- number of bytes from the header name and the requested number of bytes from
96
- the header value. It can be used even when the header name and value don't
97
- contain NULL terminating bytes by passing the `.name_len` or `.value_len` data
98
- in the `http_headers_s` structure.
99
58
 
59
+ /* we declare it in advance, because we reference it soon. */
60
+ static void http_response_log_finish(http_response_s *response);
61
+ /** Sends the data and destroys the response object.*/
62
+ void http_response_finish(http_response_s *response) {
63
+ static void (*const vtable[2])(http_response_s *) = {
64
+ http1_response_finish /* HTTP_V1 */,
65
+ fallback_http_response_dest /* HTTP_V2 */};
66
+ if (response->logged)
67
+ http_response_log_finish(response);
68
+ vtable[response->http_version](response);
69
+ }
70
+
71
+ /* *****************************************************************************
72
+ Writing data to the response object
73
+ ***************************************************************************** */
74
+ #define is_num(c) ((c) >= '0' && (c) <= '9')
75
+ #define num_val(c) ((c)-48)
76
+
77
+ #define invalid_cookie_char(c) \
78
+ ((c) < '!' || (c) > '~' || (c) == '=' || (c) == ' ' || (c) == ',' || \
79
+ (c) == ';')
80
+
81
+ /**
100
82
  If the header buffer is full or the headers were already sent (new headers
101
83
  cannot be sent), the function will return -1.
102
84
 
103
85
  On success, the function returns 0.
104
86
  */
105
- #undef http_response_write_header
106
- int http_response_write_header(http_response_s *response,
107
- http_headers_s header) {
108
- // check if the header is a reserved header
109
- if ((header.name_length == 4 || header.name_length == 0) &&
110
- strncasecmp(header.name, "date", 4) == 0)
111
- response->metadata.date_written = 1;
112
- else if ((header.name_length == 10 || header.name_length == 0) &&
113
- strncasecmp(header.name, "connection", 10) == 0) {
114
- response->metadata.connection_written = 1;
115
- if (header.value && header.value[0] == 'c')
116
- response->metadata.should_close = 1;
117
- } else if ((header.name_length == 14 || header.name_length == 0) &&
118
- strncasecmp(header.name, "content-length", 14) == 0)
119
- response->metadata.content_length_written = 1;
87
+ int http_response_write_header_fn(http_response_s *response,
88
+ http_header_s header) {
89
+ static int (*const vtable[2])(http_response_s *, http_header_s) = {
90
+ http1_response_write_header_fn /* HTTP_V1 */, NULL /* HTTP_V2 */,
91
+ };
92
+ if (!header.name || response->headers_sent)
93
+ return -1;
94
+ if (header.value && !header.value_len)
95
+ header.value_len = strlen(header.value);
96
+ if (header.name && !header.name_len)
97
+ header.name_len = strlen(header.name);
98
+ if (header.name_len == 4 && !strncasecmp(header.name, "Date", 4))
99
+ response->date_written = 1;
100
+ else if (header.name_len == 14 &&
101
+ !strncasecmp(header.name, "content-length", 14))
102
+ response->content_length_written = 1;
103
+ else if (header.name_len == 13 &&
104
+ !strncasecmp(header.name, "Last-Modified", 13))
105
+ response->date_written = 1;
106
+ else if (header.name_len == 10 &&
107
+ !strncasecmp(header.name, "connection", 10)) {
108
+ response->connection_written = 1;
109
+ if (header.value_len == 5 && !strncasecmp(header.value, "close", 5))
110
+ response->should_close = 1;
111
+ }
120
112
 
121
- // write the header to the protocol
122
- forward_func(response, response_write_header, response, header);
113
+ return vtable[response->http_version](response, header);
123
114
  }
124
- #define http_response_write_header(response, ...) \
125
- http_response_write_header(response, (http_headers_s){__VA_ARGS__})
126
115
 
127
116
  /**
128
117
  Set / Delete a cookie using this helper function.
129
118
 
130
- To set a cookie, use (in this example, a session cookie):
131
-
132
- http_response_set_cookie(response,
133
- .name = "my_cookie",
134
- .value = "data");
135
-
136
- To delete a cookie, use:
137
-
138
- http_response_set_cookie(response,
139
- .name = "my_cookie",
140
- .value = NULL);
141
-
142
- This function writes a cookie header to the response. Only the requested
143
- number of bytes from the cookie value and name are written (if none are
144
- provided, a terminating NULL byte is assumed).
145
-
146
- Both the name and the value of the cookie are checked for validity (legal
147
- characters), but other properties aren't reviewed (domain/path) - please make
148
- sure to use only valid data, as HTTP imposes restrictions on these things.
149
-
150
119
  If the header buffer is full or the headers were already sent (new headers
151
120
  cannot be sent), the function will return -1.
152
121
 
@@ -154,19 +123,48 @@ On success, the function returns 0.
154
123
  */
155
124
  #undef http_response_set_cookie
156
125
  int http_response_set_cookie(http_response_s *response, http_cookie_s cookie) {
157
- forward_func(response, response_set_cookie, response, cookie);
158
- };
126
+ /* validate common requirements. */
127
+ if (!cookie.name || response->headers_sent)
128
+ return -1;
129
+ ssize_t tmp = cookie.name_len;
130
+ if (cookie.name_len) {
131
+ do {
132
+ tmp--;
133
+ if (!cookie.name[tmp] || invalid_cookie_char(cookie.name[tmp]))
134
+ goto error;
135
+ } while (tmp);
136
+ } else {
137
+ while (cookie.name[cookie.name_len] &&
138
+ !invalid_cookie_char(cookie.name[cookie.name_len]))
139
+ cookie.name_len++;
140
+ if (cookie.name[cookie.name_len])
141
+ goto error;
142
+ }
143
+ if (cookie.value_len) {
144
+ ssize_t tmp = cookie.value_len;
145
+ do {
146
+ tmp--;
147
+ if (!cookie.value[tmp] || invalid_cookie_char(cookie.value[tmp]))
148
+ goto error;
149
+ } while (tmp);
150
+ } else {
151
+ while (cookie.value[cookie.value_len] &&
152
+ !invalid_cookie_char(cookie.value[cookie.value_len]))
153
+ cookie.value_len++;
154
+ if (cookie.value[cookie.value_len])
155
+ return -1;
156
+ }
159
157
 
160
- /**
161
- Indicates that any pending data (i.e. unsent headers) should be sent and that no
162
- more use of the response object will be made.
163
- */
164
- void http_response_finish(http_response_s *response) {
165
- perform_func(response, response_finish, response);
166
- if (response->metadata.logged)
167
- http_response_log_finish(response);
168
- http_response_destroy(response);
158
+ static int (*const vtable[2])(http_response_s *, http_cookie_s) = {
159
+ http1_response_set_cookie /* HTTP_V1 */, NULL /* HTTP_V2 */,
160
+ };
161
+ return vtable[response->http_version](response, cookie);
162
+ error:
163
+ fprintf(stderr, "ERROR: Invalid cookie value cookie value character: %c\n",
164
+ cookie.value[tmp]);
165
+ return -1;
169
166
  }
167
+
170
168
  /**
171
169
  Sends the headers (if they weren't previously sent) and writes the data to the
172
170
  underlying socket.
@@ -178,37 +176,37 @@ the function returns 0.
178
176
  */
179
177
  int http_response_write_body(http_response_s *response, const char *body,
180
178
  size_t length) {
181
- forward_func(response, response_write_body, response, body, length);
179
+ static int (*const vtable[2])(http_response_s *, const char *, size_t) = {
180
+ http1_response_write_body /* HTTP_V1 */, NULL /* HTTP_V2 */,
181
+ };
182
+ if (!response->content_length)
183
+ response->content_length = length;
184
+ return vtable[response->http_version](response, body, length);
182
185
  }
186
+
183
187
  /**
184
188
  Sends the headers (if they weren't previously sent) and writes the data to the
185
189
  underlying socket.
186
190
 
187
- The server's outgoing buffer will take ownership of the file and close it
188
- using `fclose` once the data was sent.
189
-
190
191
  If the connection was already closed, the function will return -1. On success,
191
192
  the function returns 0.
192
193
  */
193
194
  int http_response_sendfile(http_response_s *response, int source_fd,
194
195
  off_t offset, size_t length) {
195
- forward_func(response, response_sendfile, response, source_fd, offset,
196
- length);
196
+ static int (*const vtable[2])(http_response_s *, int, off_t, size_t) = {
197
+ http1_response_sendfile /* HTTP_V1 */, NULL /* HTTP_V2 */,
198
+ };
199
+ if (!response->content_length)
200
+ response->content_length = length;
201
+ return vtable[response->http_version](response, source_fd, offset, length);
197
202
  }
198
203
  /**
199
- Attempts to send the file requested using an **optional** response object (if no
200
- response object is pointed to, a temporary response object will be created).
201
-
202
- If a `file_path_unsafe` is provided, it will be appended to the `file_path_safe`
203
- (if any) and URL decoded before attempting to locate and open the file. Any
204
- insecure path manipulations in the `file_path_unsafe` (i.e. `..` or `//`) will
205
- cause the function to fail.
204
+ Attempts to send the file requested using an **optional** response object (if
205
+ no response object is pointed to, a temporary response object will be
206
+ created).
206
207
 
207
- `file_path_unsafe` MUST begine with a `/`, or it will be appended to
208
- `file_path_safe` as part of the last folder's name. if `file_path_safe` ends
209
- with a `/`, it will be trancated.
210
-
211
- If the `log` flag is set, response logging will be performed.
208
+ This function will honor Ranged requests by setting the byte range
209
+ appropriately.
212
210
 
213
211
  On failure, the function will return -1 (no response will be sent).
214
212
 
@@ -222,7 +220,6 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
222
220
  char buffer[64]; /* we'll need this a few times along the way */
223
221
  if (request == NULL || (file_path_safe == NULL && file_path_unsafe == NULL))
224
222
  return -1;
225
- http_response_s tmp_response;
226
223
 
227
224
  if (file_path_safe && path_safe_len == 0)
228
225
  path_safe_len = strlen(file_path_safe);
@@ -232,11 +229,15 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
232
229
 
233
230
  const char *mime = NULL;
234
231
  const char *ext = NULL;
235
- struct stat file_data = {};
232
+ int8_t should_free_response = 0;
233
+ struct stat file_data = {.st_size = 0};
236
234
  // fprintf(stderr, "\n\noriginal request path: %s\n", req->path);
237
- char *fname = malloc(path_safe_len + path_unsafe_len + 1 + 11);
238
- if (fname == NULL)
235
+ // char *fname = malloc(path_safe_len + path_unsafe_len + 1 + 11);
236
+ if ((path_safe_len + path_unsafe_len) >= (PATH_MAX - 1 - 11))
239
237
  return -1;
238
+ char fname[path_safe_len + path_unsafe_len + 1 + 11];
239
+ // if (fname == NULL)
240
+ // return -1;
240
241
  if (file_path_safe)
241
242
  memcpy(fname, file_path_safe, path_safe_len);
242
243
  fname[path_safe_len] = 0;
@@ -276,27 +277,27 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
276
277
  goto no_file;
277
278
 
278
279
  if (response == NULL) {
279
- response = &tmp_response;
280
- tmp_response = http_response_init(request);
280
+ should_free_response = 1;
281
+ response = http_response_create(request);
281
282
  if (log)
282
283
  http_response_log_start(response);
283
284
  }
284
285
 
285
286
  // we have a file, time to handle response details.
286
287
  int file = open(fname, O_RDONLY);
288
+ // free the allocated fname memory
289
+ // free(fname);
290
+ // fname = NULL;
287
291
  if (file == -1) {
288
- goto no_file;
292
+ goto no_fd_available;
289
293
  }
290
- // free the allocated fname memory
291
- free(fname);
292
- fname = NULL;
293
294
 
294
295
  // get the mime type (we have an ext pointer and the string isn't empty)
295
296
  if (ext && ext[1]) {
296
297
  mime = http_response_ext2mime(ext + 1);
297
298
  if (mime) {
298
299
  http_response_write_header(response, .name = "Content-Type",
299
- .name_length = 12, .value = mime);
300
+ .name_len = 12, .value = mime);
300
301
  }
301
302
  }
302
303
  /* add ETag */
@@ -304,28 +305,25 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
304
305
  sip ^= file_data.st_mtime;
305
306
  sip = siphash24(&sip, sizeof(uint64_t), SIPHASH_DEFAULT_KEY);
306
307
  bscrypt_base64_encode(buffer, (void *)&sip, 8);
307
- http_response_write_header(response, .name = "ETag", .name_length = 4,
308
- .value = buffer, .value_length = 12);
308
+ http_response_write_header(response, .name = "ETag", .name_len = 4,
309
+ .value = buffer, .value_len = 12);
309
310
 
310
311
  response->last_modified = file_data.st_mtime;
311
- http_response_write_header(response, .name = "Cache-Control",
312
- .name_length = 13, .value = "max-age=3600",
313
- .value_length = 12);
312
+ http_response_write_header(response, .name = "Cache-Control", .name_len = 13,
313
+ .value = "max-age=3600", .value_len = 12);
314
314
 
315
315
  /* check etag */
316
- if ((ext = http_request_find_header(request, "if-none-match", 13)) &&
316
+ if ((ext = http_request_header_find(request, "if-none-match", 13).value) &&
317
317
  memcmp(ext, buffer, 12) == 0) {
318
318
  /* send back 304 */
319
319
  response->status = 304;
320
320
  close(file);
321
- perform_func(response, response_finish, response);
322
- if (log)
323
- http_response_log_finish(response);
321
+ http_response_finish(response);
324
322
  return 0;
325
323
  }
326
324
 
327
325
  // Range handling
328
- if ((ext = http_request_find_header(request, "range", 5)) &&
326
+ if ((ext = http_request_header_find(request, "range", 5).value) &&
329
327
  (ext[0] | 32) == 'b' && (ext[1] | 32) == 'y' && (ext[2] | 32) == 't' &&
330
328
  (ext[3] | 32) == 'e' && (ext[4] | 32) == 's' && (ext[5] | 32) == '=') {
331
329
  // ext holds the first range, starting on index 6 i.e. RANGE: bytes=0-1
@@ -360,50 +358,55 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
360
358
  *(pos++) = '/';
361
359
  pos += http_ul2a(pos, file_data.st_size);
362
360
  http_response_write_header(response, .name = "Content-Range",
363
- .name_length = 13, .value = buffer,
364
- .value_length = pos - buffer);
361
+ .name_len = 13, .value = buffer,
362
+ .value_len = pos - buffer);
365
363
  response->status = 206;
366
364
  http_response_write_header(response, .name = "Accept-Ranges",
367
- .name_length = 13, .value = "bytes",
368
- .value_length = 5);
365
+ .name_len = 13, .value = "bytes",
366
+ .value_len = 5);
369
367
 
370
368
  if (*((uint32_t *)request->method) == *((uint32_t *)HEAD)) {
371
369
  response->content_length = 0;
372
370
  close(file);
373
- perform_func(response, response_finish, response);
374
- if (log)
375
- http_response_log_finish(response);
371
+ http_response_finish(response);
376
372
  return 0;
377
373
  }
378
374
 
379
375
  http_response_sendfile(response, file, start, finish - start + 1);
380
- if (log)
381
- http_response_log_finish(response);
376
+ http_response_finish(response);
382
377
  return 0;
383
378
  }
384
379
 
385
380
  invalid_range:
386
- http_response_write_header(response, .name = "Accept-Ranges",
387
- .name_length = 13, .value = "none",
388
- .value_length = 4);
381
+ http_response_write_header(response, .name = "Accept-Ranges", .name_len = 13,
382
+ .value = "none", .value_len = 4);
389
383
 
390
384
  if (*((uint32_t *)request->method) == *((uint32_t *)HEAD)) {
391
385
  response->content_length = 0;
392
386
  close(file);
393
- perform_func(response, response_finish, response);
394
- if (log)
395
- http_response_log_finish(response);
387
+ http_response_finish(response);
396
388
  return 0;
397
389
  }
398
390
 
399
391
  http_response_sendfile(response, file, 0, file_data.st_size);
400
- if (log)
401
- http_response_log_finish(response);
392
+ http_response_finish(response);
402
393
  return 0;
394
+
395
+ no_fd_available:
396
+ response->status = 503;
397
+ const char *body = http_response_status_str(503);
398
+ http_response_write_body(response, body, strlen(body));
399
+ http_response_finish(response);
400
+
403
401
  no_file:
404
- free(fname);
402
+ if (should_free_response && response)
403
+ http_response_destroy(response);
404
+ // free(fname);
405
405
  return -1;
406
406
  }
407
+ /* *****************************************************************************
408
+ Logging
409
+ ***************************************************************************** */
407
410
 
408
411
  #ifdef RUSAGE_SELF
409
412
  static const size_t CLOCK_RESOLUTION = 1000; /* in miliseconds */
@@ -425,39 +428,41 @@ static size_t get_clock_mili(void) {
425
428
  Starts counting miliseconds for log results.
426
429
  */
427
430
  void http_response_log_start(http_response_s *response) {
428
- response->metadata.clock_start = get_clock_mili();
429
- response->metadata.logged = 1;
431
+ response->clock_start = get_clock_mili();
432
+ response->logged = 1;
430
433
  }
431
434
  /**
432
435
  prints out the log to stderr.
433
436
  */
434
- void http_response_log_finish(http_response_s *response) {
435
- http_request_s *request = response->metadata.request;
436
- uintptr_t bytes_sent = (uintptr_t)response->metadata.headers_pos;
437
+ static void http_response_log_finish(http_response_s *response) {
438
+ http_request_s *request = response->request;
439
+ uintptr_t bytes_sent = response->content_length;
437
440
 
438
- size_t mili = response->metadata.logged
439
- ? ((get_clock_mili() - response->metadata.clock_start) /
440
- CLOCK_RESOLUTION)
441
- : 0;
441
+ size_t mili =
442
+ response->logged
443
+ ? ((get_clock_mili() - response->clock_start) / CLOCK_RESOLUTION)
444
+ : 0;
442
445
  struct tm tm;
443
- struct sockaddr_in addrinfo;
444
- socklen_t addrlen = sizeof(addrinfo);
445
- time_t last_tick = server_last_tick();
446
+ time_t last_tick = facil_last_tick();
446
447
  http_gmtime(&last_tick, &tm);
447
448
 
448
449
  // TODO Guess IP address from headers (forwarded) where possible
450
+ sock_peer_addr_s addrinfo = sock_peer_addr(response->fd);
449
451
 
450
- int got_add = getpeername(sock_uuid2fd(request->metadata.fd),
451
- (struct sockaddr *)&addrinfo, &addrlen);
452
452
  #define HTTP_REQUEST_LOG_LIMIT 128
453
453
  char buffer[HTTP_REQUEST_LOG_LIMIT];
454
- char *tmp;
455
- size_t pos;
456
- if (got_add == 0) {
457
- tmp = inet_ntoa(addrinfo.sin_addr);
458
- pos = strlen(tmp);
459
- memcpy(buffer, tmp, pos);
460
- } else {
454
+ size_t pos = 0;
455
+ if (addrinfo.addrlen) {
456
+ if (inet_ntop(
457
+ addrinfo.addr->sa_family,
458
+ addrinfo.addr->sa_family == AF_INET
459
+ ? (void *)&((struct sockaddr_in *)addrinfo.addr)->sin_addr
460
+ : (void *)&((struct sockaddr_in6 *)addrinfo.addr)->sin6_addr,
461
+ buffer, 128))
462
+ pos = strlen(buffer);
463
+ // pos = addrinfo.addr->sa_family == AF_INET ?: fmt_ip6()
464
+ }
465
+ if (pos == 0) {
461
466
  memcpy(buffer, "[unknown]", 9);
462
467
  pos = 9;
463
468
  }
@@ -519,19 +524,18 @@ void http_response_log_finish(http_response_s *response) {
519
524
 
520
525
  buffer[pos++] = ' ';
521
526
  pos += http_ul2a(buffer + pos, bytes_sent);
522
- if (response->metadata.logged) {
527
+ if (response->logged) {
523
528
  buffer[pos++] = ' ';
524
529
  pos += http_ul2a(buffer + pos, mili);
525
530
  buffer[pos++] = 'm';
526
531
  buffer[pos++] = 's';
527
532
  }
528
533
  buffer[pos++] = '\n';
529
- response->metadata.logged = 0;
534
+ response->logged = 0;
530
535
  fwrite(buffer, 1, pos, stderr);
531
536
  }
532
-
533
537
  /* *****************************************************************************
534
- Hardcded lists / matching
538
+ List matching (status + mime-type)
535
539
  *****************************************************************************
536
540
  */
537
541