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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +36 -3
- data/bin/config.ru +23 -2
- data/bin/http-hello +1 -1
- data/bin/ws-shootout +5 -0
- data/ext/iodine/defer.c +468 -0
- data/ext/iodine/defer.h +105 -0
- data/ext/iodine/evio.c +263 -0
- data/ext/iodine/evio.h +133 -0
- data/ext/iodine/extconf.rb +2 -1
- data/ext/iodine/facil.c +958 -0
- data/ext/iodine/facil.h +423 -0
- data/ext/iodine/http.c +90 -0
- data/ext/iodine/http.h +50 -12
- data/ext/iodine/http1.c +200 -267
- data/ext/iodine/http1.h +17 -26
- data/ext/iodine/http1_request.c +81 -0
- data/ext/iodine/http1_request.h +58 -0
- data/ext/iodine/http1_response.c +403 -0
- data/ext/iodine/http1_response.h +90 -0
- data/ext/iodine/http1_simple_parser.c +124 -108
- data/ext/iodine/http1_simple_parser.h +8 -3
- data/ext/iodine/http_request.c +104 -0
- data/ext/iodine/http_request.h +58 -102
- data/ext/iodine/http_response.c +212 -208
- data/ext/iodine/http_response.h +89 -252
- data/ext/iodine/iodine_core.c +57 -46
- data/ext/iodine/iodine_core.h +3 -1
- data/ext/iodine/iodine_http.c +105 -81
- data/ext/iodine/iodine_websocket.c +17 -13
- data/ext/iodine/iodine_websocket.h +1 -0
- data/ext/iodine/rb-call.c +9 -7
- data/ext/iodine/{rb-libasync.h → rb-defer.c} +57 -49
- data/ext/iodine/rb-rack-io.c +12 -6
- data/ext/iodine/rb-rack-io.h +1 -1
- data/ext/iodine/rb-registry.c +5 -2
- data/ext/iodine/sock.c +1159 -0
- data/ext/iodine/{libsock.h → sock.h} +138 -142
- data/ext/iodine/spnlock.inc +77 -0
- data/ext/iodine/websockets.c +101 -112
- data/ext/iodine/websockets.h +38 -19
- data/iodine.gemspec +3 -3
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -6
- metadata +23 -19
- data/ext/iodine/http_response_http1.h +0 -382
- data/ext/iodine/libasync.c +0 -570
- data/ext/iodine/libasync.h +0 -122
- data/ext/iodine/libreact.c +0 -350
- data/ext/iodine/libreact.h +0 -244
- data/ext/iodine/libserver.c +0 -957
- data/ext/iodine/libserver.h +0 -481
- data/ext/iodine/libsock.c +0 -1025
- 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(
|
40
|
-
|
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
|
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
|
+
}
|
data/ext/iodine/http_request.h
CHANGED
@@ -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
|
8
|
-
#define
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
#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 */
|
data/ext/iodine/http_response.c
CHANGED
@@ -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 <
|
21
|
-
#include <
|
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
|
-
|
26
|
+
Fallbacks
|
31
27
|
***************************************************************************** */
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
39
|
+
Initialization
|
52
40
|
***************************************************************************** */
|
53
41
|
|
54
|
-
/**
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
89
|
-
|
90
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
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
|
-
|
196
|
-
|
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
|
200
|
-
response object is pointed to, a temporary response object will be
|
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
|
-
|
208
|
-
|
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
|
-
|
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 (
|
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
|
-
|
280
|
-
|
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
|
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
|
-
.
|
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", .
|
308
|
-
.value = buffer, .
|
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
|
-
.
|
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 =
|
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
|
-
|
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 =
|
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
|
-
.
|
364
|
-
.
|
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
|
-
.
|
368
|
-
.
|
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
|
-
|
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
|
-
|
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
|
-
.
|
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
|
-
|
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
|
-
|
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
|
-
|
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->
|
429
|
-
response->
|
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->
|
436
|
-
uintptr_t bytes_sent =
|
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 =
|
439
|
-
|
440
|
-
|
441
|
-
|
441
|
+
size_t mili =
|
442
|
+
response->logged
|
443
|
+
? ((get_clock_mili() - response->clock_start) / CLOCK_RESOLUTION)
|
444
|
+
: 0;
|
442
445
|
struct tm tm;
|
443
|
-
|
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
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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->
|
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->
|
534
|
+
response->logged = 0;
|
530
535
|
fwrite(buffer, 1, pos, stderr);
|
531
536
|
}
|
532
|
-
|
533
537
|
/* *****************************************************************************
|
534
|
-
|
538
|
+
List matching (status + mime-type)
|
535
539
|
*****************************************************************************
|
536
540
|
*/
|
537
541
|
|