iodine 0.1.21 → 0.2.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
@@ -0,0 +1,393 @@
1
+ #ifndef HTTP_RESPONSE
2
+ /**
3
+ The HttpResponse library
4
+ ========================
5
+
6
+ This library helps us to write HTTP valid responses, even when we do not know
7
+ the internals of the HTTP protocol.
8
+
9
+ The response object allows us to easily update the response status (all
10
+ responses start with the default 200 "OK" status code), write headers and cookie
11
+ data to the header buffer and send the response's body.
12
+
13
+ The response object also allows us to easily update the body size and send body
14
+ data or open files (which will be automatically closed once sending is done).
15
+
16
+ As example flow for the response could be:
17
+
18
+ ; // get an initialized HttpRequest object
19
+ struct HttpRequest * response = HttpResponse.create(request);
20
+ ; // ... write headers and body, i.e.
21
+ HttpResponse.write_header_cstr(response, "X-Data", "my data");
22
+ HttpResponse.write_body(response, "Hello World!\r\n", 14);
23
+ ; // release the object
24
+ HttpResponse.destroy(response);
25
+
26
+
27
+ --
28
+ Thread-safety:
29
+
30
+ The response object and it's API are NOT thread-safe (it is assumed that no two
31
+ threads handle the same response at the same time).
32
+
33
+ Also, the response object will link itself to a libsock buffer packet, so it
34
+ should be created and dispatched during the same event - `sock_packet_s` objects
35
+ shouldn't be held across events or for a period of time... In other words:
36
+
37
+ **Create the response object only when you are ready to send a response**.
38
+
39
+ ---
40
+ Misc notes:
41
+ The response header's buffer size is limited and too many headers will fail the
42
+ response.
43
+
44
+ The response object allows us to easily update the response status (all
45
+ responses start with the default 200 "OK" status code), write headers and write
46
+ cookie data to the header buffer.
47
+
48
+ The response object also allows us to easily update the body size and send body
49
+ data or open files (which will be automatically closed once sending is done).
50
+
51
+ The response does NOT support chuncked encoding.
52
+
53
+ The following is the response API container, use:
54
+
55
+ struct HttpRequest * response = HttpResponse.create(request);
56
+
57
+
58
+ ---
59
+ Performance:
60
+
61
+ A note about using this library with the HTTP/1 protocol family (if this library
62
+ supports HTTP/2, in the future, the use of the response object will be required,
63
+ as it might not be possible to handle the response manually):
64
+
65
+ Since this library safeguards against certain mistakes and manages an
66
+ internal header buffer, it comes at a performance cost (it adds a layer of data
67
+ copying to the headers).
68
+
69
+ This cost is mitigated by the optional use of a response object pool, so that it
70
+ actually saves us from using `malloc` for the headers - for some cases this is
71
+ faster.
72
+
73
+ In my performance tests, the greatest issue is this: spliting the headers from
74
+ the body means that the socket's buffer is under-utilized on the first call to
75
+ `send`, while sending the headers. While other operations incure minor costs,
76
+ this is the actual reason for degraded performance when using this library.
77
+
78
+ The order of performance should be considered as follows:
79
+
80
+ 1. Destructive: Overwriting the request's header buffer with both the response
81
+ headers and the response data (small responses). Sending the data through the
82
+ socket using the `Server.write` function.
83
+
84
+ 2. Using malloc to allocate enough memory for both the response's headers AND
85
+ it's body. Sending the data through the socket using the `Server.write_move`
86
+ function.
87
+
88
+ 3. Using the HttpResponse object to send the response.
89
+
90
+ Network issues and response properties might influence the order of performant
91
+ solutions.
92
+ */
93
+ #define HTTP_RESPONSE
94
+ #include "http.h"
95
+ #include "http_request.h"
96
+
97
+ typedef struct {
98
+ /**
99
+ The body's response length.
100
+
101
+ If this isn't set manually, the first call to
102
+ `HttpResponse.write_body` (and friends) will set the length to the length
103
+ being written (which might be less then the total data sent, if the sending is
104
+ fragmented).
105
+
106
+ Set the value to -1 to force the HttpResponse not to write nor automate the
107
+ `Content-Length` header.
108
+ */
109
+ ssize_t content_length;
110
+ /**
111
+ The HTTP date for the response (in seconds since epoche).
112
+
113
+ Defaults to now (approximately, not exactly, uses cached data).
114
+
115
+ The date will be automatically formatted to match the HTTP protocol
116
+ specifications. It is better to avoid setting the "Date" header manualy.
117
+ */
118
+ time_t date;
119
+ /**
120
+ The HTTP date for the response (in seconds since epoche).
121
+
122
+ Defaults to now (approximately, not exactly, uses cached data).
123
+
124
+ The date will be automatically formatted to match the HTTP protocol
125
+ specifications. It is better to avoid setting the "Date" header manualy.
126
+ */
127
+ time_t last_modified;
128
+ /**
129
+ The response status
130
+ */
131
+ uint16_t status;
132
+ /**
133
+ Metadata about the response's state - don't edit this data (except the opaque
134
+ data, if needed).
135
+ */
136
+ struct {
137
+ /**
138
+ The request object to which this response is "responding".
139
+ */
140
+ http_request_s *request;
141
+ /**
142
+ The libsock fd UUID.
143
+ */
144
+ intptr_t fd;
145
+ /**
146
+ A `libsock` buffer packet used for header data (to avoid double copy).
147
+ */
148
+ sock_packet_s *packet;
149
+ /**
150
+ A pointer to the header's writing position.
151
+ */
152
+ char *headers_pos;
153
+ /**
154
+ Internally used by the logging API.
155
+ */
156
+ clock_t clock_start;
157
+ /**
158
+ HTTP protocol version identifier.
159
+ */
160
+ uint8_t version;
161
+ /**
162
+ Set to true once the headers were sent.
163
+ */
164
+ unsigned headers_sent : 1;
165
+ /**
166
+ Set to true when the "Date" header is written to the buffer.
167
+ */
168
+ unsigned date_written : 1;
169
+ /**
170
+ Set to true when the "Connection" header is written to the buffer.
171
+ */
172
+ unsigned connection_written : 1;
173
+ /**
174
+ Set to true when the "Content-Length" header is written to the buffer.
175
+ */
176
+ unsigned content_length_written : 1;
177
+ /**
178
+ Set to true in order to close the connection once the response was sent.
179
+ */
180
+ unsigned should_close : 1;
181
+ /**
182
+ Internally used by the logging API.
183
+ */
184
+ unsigned logged : 1;
185
+ /**
186
+ Reserved for future use.
187
+ */
188
+ unsigned rsrv : 2;
189
+
190
+ } metadata;
191
+
192
+ } http_response_s;
193
+
194
+ /**
195
+ The struct HttpCookie is a helper for seting cookie data.
196
+
197
+ This struct is used together with the `HttpResponse.set_cookie`. i.e.:
198
+
199
+ HttpResponse.set_cookie(response, (struct HttpCookie){
200
+ .name = "my_cookie",
201
+ .value = "data"
202
+ });
203
+
204
+ */
205
+ typedef struct {
206
+ /** The cookie's name (key). */
207
+ char *name;
208
+ /** The cookie's value (leave blank to delete cookie). */
209
+ char *value;
210
+ /** The cookie's domain (optional). */
211
+ char *domain;
212
+ /** The cookie's path (optional). */
213
+ char *path;
214
+ /** The cookie name's size in bytes or a terminating NULL will be assumed.*/
215
+ size_t name_len;
216
+ /** The cookie value's size in bytes or a terminating NULL will be assumed.*/
217
+ size_t value_len;
218
+ /** The cookie domain's size in bytes or a terminating NULL will be assumed.*/
219
+ size_t domain_len;
220
+ /** The cookie path's size in bytes or a terminating NULL will be assumed.*/
221
+ size_t path_len;
222
+ /** Max Age (how long should the cookie persist), in seconds (0 == session).*/
223
+ int max_age;
224
+ /** Limit cookie to secure connections.*/
225
+ unsigned secure : 1;
226
+ /** Limit cookie to HTTP (intended to prevent javascript access/hijacking).*/
227
+ unsigned http_only : 1;
228
+ } http_cookie_s;
229
+
230
+ /**
231
+ Initializes a response object with the request object. This function assumes the
232
+ response object memory is garbage and might have been stack-allocated.
233
+
234
+ Notice that the `http_request_s` pointer must point at a valid request object
235
+ and that the request object must remain valid until the response had been
236
+ completed.
237
+
238
+ Hangs on failuer (waits for available resources).
239
+ */
240
+ http_response_s http_response_init(http_request_s *request);
241
+ /**
242
+ Releases any resources held by the response object (doesn't release the response
243
+ object itself, which might have been allocated on the stack).
244
+
245
+ This function assumes the response object might have been stack-allocated.
246
+ */
247
+ void http_response_destroy(http_response_s *response);
248
+ /** Gets a response status, as a string. */
249
+ const char *http_response_status_str(uint16_t status);
250
+ /** Gets the mime-type string (C string) associated with the file extension. */
251
+ const char *http_response_ext2mime(const char *ext);
252
+ /**
253
+ Writes a header to the response. This function writes only the requested
254
+ number of bytes from the header name and the requested number of bytes from
255
+ the header value. It can be used even when the header name and value don't
256
+ contain NULL terminating bytes by passing the `.name_len` or `.value_len` data
257
+ in the `http_headers_s` structure.
258
+
259
+ If the header buffer is full or the headers were already sent (new headers
260
+ cannot be sent), the function will return -1.
261
+
262
+ On success, the function returns 0.
263
+ */
264
+ int http_response_write_header(http_response_s *, http_headers_s header);
265
+ #define http_response_write_header(response, ...) \
266
+ http_response_write_header(response, (http_headers_s){__VA_ARGS__})
267
+
268
+ /**
269
+ Set / Delete a cookie using this helper function.
270
+
271
+ To set a cookie, use (in this example, a session cookie):
272
+
273
+ http_response_set_cookie(response,
274
+ .name = "my_cookie",
275
+ .value = "data");
276
+
277
+ To delete a cookie, use:
278
+
279
+ http_response_set_cookie(response,
280
+ .name = "my_cookie",
281
+ .value = NULL);
282
+
283
+ This function writes a cookie header to the response. Only the requested
284
+ number of bytes from the cookie value and name are written (if none are
285
+ provided, a terminating NULL byte is assumed).
286
+
287
+ Both the name and the value of the cookie are checked for validity (legal
288
+ characters), but other properties aren't reviewed (domain/path) - please make
289
+ sure to use only valid data, as HTTP imposes restrictions on these things.
290
+
291
+ If the header buffer is full or the headers were already sent (new headers
292
+ cannot be sent), the function will return -1.
293
+
294
+ On success, the function returns 0.
295
+ */
296
+ int http_response_set_cookie(http_response_s *, http_cookie_s);
297
+ #define http_response_set_cookie(response, ...) \
298
+ http_response_set_cookie(response, (http_cookie_s){__VA_ARGS__})
299
+
300
+ /**
301
+ Indicates that any pending data (i.e. unsent headers) should be sent and that no
302
+ more use of the response object will be made. This will also release any
303
+ resources aquired when the response object was initialized, similar to the
304
+ `http_response_destroy` function.
305
+
306
+ If logging was initiated and hadn't been performed, it will be performed.
307
+
308
+ If the connection was already closed, the function will return -1. On success,
309
+ the function returns 0.
310
+ */
311
+ void http_response_finish(http_response_s *);
312
+ /**
313
+ Sends the headers (if they weren't previously sent) and writes the data to the
314
+ underlying socket.
315
+
316
+ The body will be copied to the server's outgoing buffer.
317
+
318
+ If the connection was already closed, the function will return -1. On success,
319
+ the function returns 0.
320
+ */
321
+ int http_response_write_body(http_response_s *, const char *body,
322
+ size_t length);
323
+
324
+ // /**
325
+ // REVIEW: IS THIS APPLICABLE FOR HTTP/2 AS WELL? this must be a unified API.
326
+ //
327
+ // Sends the headers (if they weren't previously sent) and writes the data to
328
+ // the
329
+ // underlying socket.
330
+ //
331
+ // The server's outgoing buffer will take ownership of the body and free it's
332
+ // memory using `free` once the data was sent.
333
+ //
334
+ // If the connection was already closed, the function will return -1. On
335
+ // success,
336
+ // the function returns 0.
337
+ // */
338
+ // int http_response_write_body_move(http_response_s*,
339
+ // const char* body,
340
+ // size_t length);
341
+
342
+ /**
343
+ Sends the headers (if they weren't previously sent) and writes the data to the
344
+ underlying socket.
345
+
346
+ The server's outgoing buffer will take ownership of the file and close it
347
+ using `fclose` once the data was sent.
348
+
349
+ If the connection was already closed, the function will return -1. On success,
350
+ the function returns 0.
351
+ */
352
+ int http_response_sendfile(http_response_s *, int source_fd, off_t offset,
353
+ size_t length);
354
+ /**
355
+ Attempts to send the file requested using an **optional** response object (if no
356
+ response object is pointed to, a temporary response object will be created).
357
+
358
+ If a `file_path_unsafe` is provided, it will be appended to the `file_path_safe`
359
+ (if any) and URL decoded before attempting to locate and open the file. Any
360
+ insecure path manipulations in the `file_path_unsafe` (i.e. `..` or `//`) will
361
+ cause the function to fail.
362
+
363
+ `file_path_unsafe` MUST begine with a `/`, or it will be appended to
364
+ `file_path_safe` as part of the last folder's name. if `file_path_safe` ends
365
+ with a `/`, it will be trancated.
366
+
367
+ If the `log` flag is set, response logging will be performed.
368
+
369
+ If the path ends with a backslash ('/'), the string `"index.html"` will be
370
+ appended (a default file name for when serving a folder). No automatic folder
371
+ indexing is supported.
372
+
373
+ This function will honor Ranged requests by setting the byte range
374
+ appropriately.
375
+
376
+ On failure, the function will return -1 (no response will be sent).
377
+
378
+ On success, the function returns 0.
379
+ */
380
+ int http_response_sendfile2(http_response_s *response, http_request_s *request,
381
+ const char *file_path_safe, size_t path_safe_len,
382
+ const char *file_path_unsafe,
383
+ size_t path_unsafe_len, uint8_t log);
384
+ /**
385
+ Starts counting miliseconds for log results.
386
+ */
387
+ void http_response_log_start(http_response_s *);
388
+ /**
389
+ prints out the log to stderr.
390
+ */
391
+ void http_response_log_finish(http_response_s *);
392
+
393
+ #endif
@@ -0,0 +1,374 @@
1
+ #ifndef HTTP1_RESPONSE_FORMATTER
2
+ #define HTTP1_RESPONSE_FORMATTER
3
+
4
+ #include "http_response.h"
5
+ #include "libsock.h"
6
+
7
+ #ifndef __unused
8
+ #define __unused __attribute__((unused))
9
+ #endif
10
+
11
+ /* *****************************************************************************
12
+ Helpers
13
+ ***************************************************************************** */
14
+
15
+ /**
16
+ The padding for the status line (62 + 2 for the extra \r\n after the headers).
17
+ */
18
+ #define H1P_HEADER_START 80
19
+ #define H1P_OVERFLOW_PADDING 512
20
+
21
+ /** checks for overflow */
22
+ #define overflowing(response) \
23
+ (((response)->metadata.headers_pos - \
24
+ (char *)((response)->metadata.packet->buffer)) >= \
25
+ (BUFFER_PACKET_SIZE - H1P_OVERFLOW_PADDING))
26
+
27
+ #define HEADERS_FINISHED(response) \
28
+ ((response)->metadata.packet == NULL || (response)->metadata.headers_sent)
29
+
30
+ #define invalid_cookie_char(c) \
31
+ ((c) < '!' || (c) > '~' || (c) == '=' || (c) == ' ' || (c) == ',' || \
32
+ (c) == ';')
33
+
34
+ #define h1p_validate_hpos(response) \
35
+ { \
36
+ if ((response)->metadata.headers_pos == 0) { \
37
+ (response)->metadata.headers_pos = \
38
+ (response)->metadata.packet->buffer + H1P_HEADER_START; \
39
+ } \
40
+ }
41
+ static inline int h1p_protected_copy(http_response_s *response,
42
+ const char *data, size_t data_len) {
43
+ if (data_len > 0 && data_len < H1P_OVERFLOW_PADDING) {
44
+ memcpy(response->metadata.headers_pos, data, data_len);
45
+ response->metadata.headers_pos += data_len;
46
+ } else {
47
+ while (*data) {
48
+ if (overflowing(response))
49
+ return -1;
50
+ *(response->metadata.headers_pos++) = *(data++);
51
+ }
52
+ }
53
+ return overflowing(response);
54
+ }
55
+
56
+ /* this function assume the padding in `h1p_protected_copy` saved enough room
57
+ * for the data to be safely written.*/
58
+ __unused static inline sock_packet_s *
59
+ h1p_finalize_headers(http_response_s *response) {
60
+ if (HEADERS_FINISHED(response))
61
+ return NULL;
62
+ h1p_validate_hpos(response);
63
+
64
+ sock_packet_s *headers = response->metadata.packet;
65
+ response->metadata.packet = NULL;
66
+ const char *status = http_response_status_str(response->status);
67
+ if (!status) {
68
+ response->status = 500;
69
+ status = http_response_status_str(response->status);
70
+ }
71
+
72
+ /* write the keep-alive (connection) header, if missing */
73
+ if (!response->metadata.connection_written) {
74
+ if (response->metadata.should_close) {
75
+ h1p_protected_copy(response, "Connection:close\r\n", 18);
76
+ } else {
77
+ h1p_protected_copy(response, "Connection:keep-alive\r\n"
78
+ "Keep-Alive:timeout=2\r\n",
79
+ 45);
80
+ }
81
+ }
82
+ /* write the content length header, unless forced not to (<0) */
83
+ if (response->metadata.content_length_written == 0 &&
84
+ !(response->content_length < 0) && response->status >= 200 &&
85
+ response->status != 204 && response->status != 304) {
86
+ h1p_protected_copy(response, "Content-Length:", 15);
87
+ response->metadata.headers_pos +=
88
+ http_ul2a(response->metadata.headers_pos, response->content_length);
89
+ /* write the header seperator (`\r\n`) */
90
+ *(response->metadata.headers_pos++) = '\r';
91
+ *(response->metadata.headers_pos++) = '\n';
92
+ }
93
+ /* write the date, if missing */
94
+ if (!response->metadata.date_written) {
95
+ if (response->date < response->last_modified)
96
+ response->date = response->last_modified;
97
+ struct tm t;
98
+ /* date header */
99
+ http_gmtime(&response->date, &t);
100
+ h1p_protected_copy(response, "Date:", 5);
101
+ response->metadata.headers_pos +=
102
+ http_date2str(response->metadata.headers_pos, &t);
103
+ *(response->metadata.headers_pos++) = '\r';
104
+ *(response->metadata.headers_pos++) = '\n';
105
+ /* last-modified header */
106
+ http_gmtime(&response->last_modified, &t);
107
+ h1p_protected_copy(response, "Last-Modified:", 14);
108
+ response->metadata.headers_pos +=
109
+ http_date2str(response->metadata.headers_pos, &t);
110
+ *(response->metadata.headers_pos++) = '\r';
111
+ *(response->metadata.headers_pos++) = '\n';
112
+ }
113
+ /* write the headers completion marker (empty line - `\r\n`) */
114
+ *(response->metadata.headers_pos++) = '\r';
115
+ *(response->metadata.headers_pos++) = '\n';
116
+
117
+ /* write the status string is "HTTP/1.1 xxx <...>\r\n" length == 15 +
118
+ * strlen(status) */
119
+
120
+ size_t tmp = strlen(status);
121
+ int start = H1P_HEADER_START - (15 + tmp);
122
+ memcpy(headers->buffer + start, "HTTP/1.1 ### ", 13);
123
+ memcpy(headers->buffer + start + 13, status, tmp);
124
+ ((char *)headers->buffer)[H1P_HEADER_START - 1] = '\n';
125
+ ((char *)headers->buffer)[H1P_HEADER_START - 2] = '\r';
126
+ tmp = response->status / 10;
127
+ *((char *)headers->buffer + start + 11) =
128
+ '0' + (response->status - (10 * tmp));
129
+ *((char *)headers->buffer + start + 10) = '0' + (tmp - (10 * (tmp / 10)));
130
+ *((char *)headers->buffer + start + 9) = '0' + (response->status / 100);
131
+
132
+ headers->buffer = (char *)headers->buffer + start;
133
+ headers->length = response->metadata.headers_pos - (char *)headers->buffer;
134
+ return headers;
135
+ }
136
+
137
+ static int h1p_send_headers(http_response_s *response, sock_packet_s *packet) {
138
+ if (packet == NULL)
139
+ return -1;
140
+ /* mark headers as sent */
141
+ response->metadata.headers_sent = 1;
142
+ response->metadata.packet = NULL;
143
+ response->metadata.headers_pos = (char *)packet->length;
144
+ /* write data to network */
145
+ return sock_send_packet(response->metadata.fd, packet);
146
+ };
147
+
148
+ /* *****************************************************************************
149
+ Implementation
150
+ ***************************************************************************** */
151
+
152
+ __unused static inline int h1p_response_write_header(http_response_s *response,
153
+ http_headers_s header) {
154
+ if (HEADERS_FINISHED(response) || header.name == NULL)
155
+ return -1;
156
+
157
+ h1p_validate_hpos(response);
158
+
159
+ if (h1p_protected_copy(response, header.name, header.name_length))
160
+ return -1;
161
+ *(response->metadata.headers_pos++) = ':';
162
+ /* *(response->metadata.headers_pos++) = ' '; -- better leave out */
163
+ if (header.value != NULL &&
164
+ h1p_protected_copy(response, header.value, header.value_length))
165
+ return -1;
166
+ *(response->metadata.headers_pos++) = '\r';
167
+ *(response->metadata.headers_pos++) = '\n';
168
+ return 0;
169
+ }
170
+
171
+ /**
172
+ Set / Delete a cookie using this helper function.
173
+ */
174
+ __unused static int h1p_response_set_cookie(http_response_s *response,
175
+ http_cookie_s cookie) {
176
+ if (HEADERS_FINISHED(response) || cookie.name == NULL ||
177
+ overflowing(response))
178
+ return -1; /* must have a cookie name. */
179
+ h1p_validate_hpos(response);
180
+
181
+ /* write the header's name to the buffer */
182
+ if (h1p_protected_copy(response, "Set-Cookie:", 11))
183
+ return -1;
184
+
185
+ /* we won't use h1p_protected_copy because we'll be testing the name and value
186
+ * for illegal characters. */
187
+
188
+ /* write the cookie name */
189
+ if (cookie.name_len && cookie.name_len < H1P_OVERFLOW_PADDING) {
190
+ for (size_t i = 0; i < cookie.name_len; i++) {
191
+ if (invalid_cookie_char(*cookie.name) == 0) {
192
+ *(response->metadata.headers_pos++) = *(cookie.name++);
193
+ continue;
194
+ } else {
195
+ fprintf(stderr, "Invalid cookie name cookie name character: %c\n",
196
+ *cookie.name);
197
+ return -1;
198
+ }
199
+ }
200
+ } else {
201
+ while (*cookie.name && overflowing(response) == 0) {
202
+ if (invalid_cookie_char(*cookie.name) == 0) {
203
+ *(response->metadata.headers_pos++) = *(cookie.name++);
204
+ continue;
205
+ } else {
206
+ fprintf(stderr, "Invalid cookie name cookie name character: %c\n",
207
+ *cookie.name);
208
+ return -1;
209
+ }
210
+ }
211
+ }
212
+ if (overflowing(response))
213
+ return -1;
214
+ /* seperate name from value */
215
+ *(response->metadata.headers_pos++) = '=';
216
+ /* write the cookie value, if any */
217
+ if (cookie.value) {
218
+ if (cookie.value_len && cookie.value_len < H1P_OVERFLOW_PADDING) {
219
+ for (size_t i = 0; i < cookie.value_len; i++) {
220
+ if (invalid_cookie_char(*cookie.value) == 0) {
221
+ *(response->metadata.headers_pos++) = *(cookie.value++);
222
+ continue;
223
+ } else {
224
+ fprintf(stderr, "Invalid cookie value cookie name character: %c\n",
225
+ *cookie.value);
226
+ return -1;
227
+ }
228
+ }
229
+ } else {
230
+ while (*cookie.value && overflowing(response) == 0) {
231
+ if (invalid_cookie_char(*cookie.value) == 0) {
232
+ *(response->metadata.headers_pos++) = *(cookie.value++);
233
+ continue;
234
+ } else {
235
+ fprintf(stderr, "Invalid cookie value cookie name character: %c\n",
236
+ *cookie.value);
237
+ return -1;
238
+ }
239
+ }
240
+ }
241
+ if (overflowing(response))
242
+ return -1;
243
+ } else {
244
+ cookie.max_age = -1;
245
+ }
246
+ /* complete value data */
247
+ *(response->metadata.headers_pos++) = ';';
248
+ if (cookie.max_age) {
249
+ response->metadata.headers_pos +=
250
+ sprintf(response->metadata.headers_pos, "Max-Age=%d;", cookie.max_age);
251
+ }
252
+ if (cookie.domain) {
253
+ memcpy(response->metadata.headers_pos, "domain=", 7);
254
+ response->metadata.headers_pos += 7;
255
+ if (h1p_protected_copy(response, cookie.domain, cookie.domain_len))
256
+ return -1;
257
+ *(response->metadata.headers_pos++) = ';';
258
+ }
259
+ if (cookie.path) {
260
+ memcpy(response->metadata.headers_pos, "path=", 5);
261
+ response->metadata.headers_pos += 5;
262
+ if (h1p_protected_copy(response, cookie.path, cookie.path_len))
263
+ return -1;
264
+ *(response->metadata.headers_pos++) = ';';
265
+ }
266
+ if (cookie.http_only) {
267
+ memcpy(response->metadata.headers_pos, "HttpOnly;", 9);
268
+ response->metadata.headers_pos += 9;
269
+ }
270
+ if (cookie.secure) {
271
+ memcpy(response->metadata.headers_pos, "secure;", 7);
272
+ response->metadata.headers_pos += 7;
273
+ }
274
+ *(response->metadata.headers_pos++) = '\r';
275
+ *(response->metadata.headers_pos++) = '\n';
276
+ return 0;
277
+ }
278
+
279
+ /**
280
+ Sends the headers (if unsent) and sends the body.
281
+ */
282
+ __unused static inline int h1p_response_write_body(http_response_s *response,
283
+ const char *body,
284
+ size_t length) {
285
+ if (!response->content_length)
286
+ response->content_length = length;
287
+ sock_packet_s *headers = h1p_finalize_headers(response);
288
+ if (headers != NULL) { /* we haven't sent the headers yet */
289
+ ssize_t i_read =
290
+ ((BUFFER_PACKET_SIZE - H1P_HEADER_START) - headers->length);
291
+ if (i_read > 1024) {
292
+ /* we can fit at least some of the data inside the response buffer. */
293
+ if (i_read > length) {
294
+ i_read = length;
295
+ /* we can fit the data inside the response buffer. */
296
+ memcpy(response->metadata.headers_pos, body, i_read);
297
+ response->metadata.headers_pos += i_read;
298
+ headers->length += i_read;
299
+ return h1p_send_headers(response, headers);
300
+ }
301
+ memcpy(response->metadata.headers_pos, body, i_read);
302
+ response->metadata.headers_pos += i_read;
303
+ headers->length += i_read;
304
+ length -= i_read;
305
+ body += i_read;
306
+ }
307
+ /* we need to send the (rest of the) body seperately. */
308
+ if (h1p_send_headers(response, headers))
309
+ return -1;
310
+ }
311
+ response->metadata.headers_pos += length;
312
+ return sock_write(response->metadata.fd, (void *)body, length);
313
+ }
314
+ /**
315
+ Sends the headers (if unsent) and schedules the file to be sent.
316
+ */
317
+ __unused static inline int h1p_response_sendfile(http_response_s *response,
318
+ int source_fd, off_t offset,
319
+ size_t length) {
320
+ if (!response->content_length)
321
+ response->content_length = length;
322
+
323
+ sock_packet_s *headers = h1p_finalize_headers(response);
324
+
325
+ if (headers != NULL) { /* we haven't sent the headers yet */
326
+ if (headers->length < (BUFFER_PACKET_SIZE - H1P_HEADER_START)) {
327
+ /* we can fit at least some of the data inside the response buffer. */
328
+ ssize_t i_read = pread(
329
+ source_fd, response->metadata.headers_pos,
330
+ ((BUFFER_PACKET_SIZE - H1P_HEADER_START) - headers->length), offset);
331
+ if (i_read > 0) {
332
+ if (i_read >= length) {
333
+ headers->length += length;
334
+ close(source_fd);
335
+ return h1p_send_headers(response, headers);
336
+ } else {
337
+ headers->length += i_read;
338
+ length -= i_read;
339
+ offset += i_read;
340
+ }
341
+ }
342
+ }
343
+ /* we need to send the rest seperately. */
344
+ if (h1p_send_headers(response, headers)) {
345
+ close(source_fd);
346
+ return -1;
347
+ }
348
+ }
349
+ response->metadata.headers_pos += length;
350
+ return sock_sendfile(response->metadata.fd, source_fd, offset, length);
351
+ }
352
+
353
+ __unused static inline int h1p_response_finish(http_response_s *response) {
354
+ sock_packet_s *headers = h1p_finalize_headers(response);
355
+ if (headers) {
356
+ return h1p_send_headers(response, headers);
357
+ }
358
+ if (response->metadata.should_close) {
359
+ sock_close(response->metadata.fd);
360
+ }
361
+
362
+ return 0;
363
+ }
364
+
365
+ /* *****************************************************************************
366
+ Cleanup
367
+ ***************************************************************************** */
368
+
369
+ /* clear any used definitions */
370
+ #undef overflowing
371
+ #undef HEADERS_FINISHED
372
+ #undef invalid_cookie_char
373
+ #undef h1p_validate_hpos
374
+ #endif