| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
static int |
|
2
|
594
|
|
|
|
|
|
try_parse_http(struct feer_conn *c, size_t last_read) |
|
3
|
|
|
|
|
|
|
{ |
|
4
|
594
|
|
|
|
|
|
struct feer_req *req = c->req; |
|
5
|
594
|
100
|
|
|
|
|
if (likely(!req)) { |
|
6
|
578
|
100
|
|
|
|
|
FEER_REQ_ALLOC(req); |
|
7
|
578
|
|
|
|
|
|
c->req = req; |
|
8
|
|
|
|
|
|
|
} |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
// phr_parse_request resets num_headers on each call; must restore to max |
|
11
|
594
|
|
|
|
|
|
req->num_headers = MAX_HEADERS; |
|
12
|
|
|
|
|
|
|
|
|
13
|
1188
|
|
|
|
|
|
return phr_parse_request(SvPVX(c->rbuf), SvCUR(c->rbuf), |
|
14
|
|
|
|
|
|
|
&req->method, &req->method_len, |
|
15
|
|
|
|
|
|
|
&req->uri, &req->uri_len, &req->minor_version, |
|
16
|
594
|
|
|
|
|
|
req->headers, &req->num_headers, |
|
17
|
594
|
|
|
|
|
|
(SvCUR(c->rbuf)-last_read)); |
|
18
|
|
|
|
|
|
|
} |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
// Finish receiving: transition to RECEIVE_SHUTDOWN and stop read I/O |
|
21
|
|
|
|
|
|
|
INLINE_UNLESS_DEBUG static void |
|
22
|
662
|
|
|
|
|
|
finish_receiving(struct feer_conn *c) { |
|
23
|
662
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_SHUTDOWN); |
|
24
|
662
|
|
|
|
|
|
stop_read_watcher(c); |
|
25
|
662
|
|
|
|
|
|
stop_read_timer(c); |
|
26
|
662
|
|
|
|
|
|
stop_header_timer(c); |
|
27
|
662
|
|
|
|
|
|
} |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
static void |
|
30
|
679
|
|
|
|
|
|
try_conn_read(EV_P_ ev_io *w, int revents) |
|
31
|
|
|
|
|
|
|
{ |
|
32
|
679
|
|
|
|
|
|
dCONN; |
|
33
|
679
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); |
|
34
|
679
|
|
|
|
|
|
feer_conn_set_busy(c); |
|
35
|
679
|
|
|
|
|
|
ssize_t got_n = 0; |
|
36
|
|
|
|
|
|
|
|
|
37
|
679
|
100
|
|
|
|
|
if (unlikely(c->pipelined)) goto pipelined; |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
// if it's marked readable EV suggests we simply try read it. Otherwise it |
|
40
|
|
|
|
|
|
|
// is stopped and we should ditch this connection. |
|
41
|
579
|
50
|
|
|
|
|
if (unlikely(revents & EV_ERROR && !(revents & EV_READ))) { |
|
|
|
0
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
trace("EV error on read, fd=%d revents=0x%08x\n", w->fd, revents); |
|
43
|
0
|
|
|
|
|
|
goto try_read_error; |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
579
|
50
|
|
|
|
|
if (unlikely(c->receiving == RECEIVE_SHUTDOWN)) |
|
47
|
0
|
|
|
|
|
|
goto dont_read_again; |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
trace("try read %d\n",w->fd); |
|
50
|
|
|
|
|
|
|
|
|
51
|
579
|
100
|
|
|
|
|
if (unlikely(!c->rbuf)) { // unlikely = optimize for keepalive requests |
|
52
|
|
|
|
|
|
|
trace("init rbuf for %d\n",w->fd); |
|
53
|
533
|
|
|
|
|
|
c->rbuf = newSV(READ_BUFSZ + 1); |
|
54
|
533
|
|
|
|
|
|
SvPOK_on(c->rbuf); |
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
|
|
57
|
579
|
|
|
|
|
|
ssize_t space_free = SvLEN(c->rbuf) - SvCUR(c->rbuf); |
|
58
|
579
|
100
|
|
|
|
|
if (unlikely(space_free < READ_BUFSZ)) { // unlikely = optimize for small |
|
59
|
27
|
|
|
|
|
|
size_t cur_len = SvLEN(c->rbuf); |
|
60
|
|
|
|
|
|
|
// DoS protection: limit buffer growth (especially for chunked encoding) |
|
61
|
27
|
50
|
|
|
|
|
if (unlikely(cur_len + READ_GROW_FACTOR*READ_BUFSZ > c->cached_max_read_buf)) { |
|
62
|
|
|
|
|
|
|
trace("buffer too large %d: %"Sz_uf" > %"Sz_uf"\n", |
|
63
|
|
|
|
|
|
|
w->fd, (Sz)cur_len, (Sz)c->cached_max_read_buf); |
|
64
|
0
|
|
|
|
|
|
respond_with_server_error(c, "Request too large\n", 413); |
|
65
|
0
|
|
|
|
|
|
goto try_read_error; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
27
|
|
|
|
|
|
size_t new_len = cur_len + READ_GROW_FACTOR*READ_BUFSZ; |
|
68
|
|
|
|
|
|
|
trace("moar memory %d: %"Sz_uf" to %"Sz_uf"\n", |
|
69
|
|
|
|
|
|
|
w->fd, (Sz)SvLEN(c->rbuf), (Sz)new_len); |
|
70
|
27
|
50
|
|
|
|
|
SvGROW(c->rbuf, new_len); |
|
|
|
50
|
|
|
|
|
|
|
71
|
27
|
|
|
|
|
|
space_free += READ_GROW_FACTOR*READ_BUFSZ; |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
|
|
74
|
579
|
|
|
|
|
|
char *cur = SvPVX(c->rbuf) + SvCUR(c->rbuf); |
|
75
|
579
|
|
|
|
|
|
got_n = read(w->fd, cur, space_free); |
|
76
|
|
|
|
|
|
|
|
|
77
|
579
|
100
|
|
|
|
|
if (unlikely(got_n <= 0)) { |
|
78
|
28
|
100
|
|
|
|
|
if (unlikely(got_n == 0)) { |
|
79
|
|
|
|
|
|
|
trace("EOF before complete request: fd=%d buf=%"Sz_uf"\n", w->fd, (Sz)SvCUR(c->rbuf)); |
|
80
|
26
|
|
|
|
|
|
goto try_read_error; |
|
81
|
|
|
|
|
|
|
} |
|
82
|
2
|
50
|
|
|
|
|
if (likely(errno == EAGAIN || errno == EINTR)) |
|
|
|
50
|
|
|
|
|
|
|
83
|
0
|
|
|
|
|
|
goto try_read_again; |
|
84
|
2
|
|
|
|
|
|
trouble("try_conn_read fd=%d: %s\n", w->fd, strerror(errno)); |
|
85
|
2
|
|
|
|
|
|
goto try_read_error; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
trace("read %d %"Ssz_df"\n", w->fd, (Ssz)got_n); |
|
89
|
551
|
|
|
|
|
|
SvCUR(c->rbuf) += got_n; |
|
90
|
551
|
100
|
|
|
|
|
if (c->receiving == RECEIVE_WAIT) |
|
91
|
6
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_HEADERS); |
|
92
|
551
|
|
|
|
|
|
goto try_parse; |
|
93
|
|
|
|
|
|
|
|
|
94
|
100
|
|
|
|
|
|
pipelined: |
|
95
|
100
|
|
|
|
|
|
got_n = c->pipelined; |
|
96
|
100
|
|
|
|
|
|
c->pipelined = 0; |
|
97
|
|
|
|
|
|
|
|
|
98
|
651
|
|
|
|
|
|
try_parse: |
|
99
|
|
|
|
|
|
|
// Handle PROXY protocol header state |
|
100
|
690
|
100
|
|
|
|
|
if (unlikely(c->receiving == RECEIVE_PROXY_HEADER)) { |
|
101
|
131
|
|
|
|
|
|
int ret = try_parse_proxy_header(c); |
|
102
|
131
|
100
|
|
|
|
|
if (ret == -1) { |
|
103
|
92
|
|
|
|
|
|
respond_with_server_error(c, "Invalid PROXY protocol header\n", 400); |
|
104
|
92
|
|
|
|
|
|
goto dont_read_again; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
39
|
50
|
|
|
|
|
if (ret == -2) goto try_read_again_reset_timer; |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
// Consume parsed bytes from buffer |
|
109
|
39
|
|
|
|
|
|
STRLEN remaining = SvCUR(c->rbuf) - ret; |
|
110
|
39
|
50
|
|
|
|
|
if (remaining > 0) |
|
111
|
39
|
|
|
|
|
|
memmove(SvPVX(c->rbuf), SvPVX(c->rbuf) + ret, remaining); |
|
112
|
39
|
|
|
|
|
|
SvCUR_set(c->rbuf, remaining); |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
// Clear cached remote addr/port (force regeneration with new address) |
|
115
|
39
|
50
|
|
|
|
|
feer_clear_remote_cache(c); |
|
|
|
50
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
// Transition to HTTP parsing |
|
118
|
39
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_HEADERS); |
|
119
|
39
|
50
|
|
|
|
|
if (remaining > 0) { |
|
120
|
39
|
|
|
|
|
|
got_n = remaining; |
|
121
|
39
|
|
|
|
|
|
goto try_parse; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
0
|
|
|
|
|
|
goto try_read_again_reset_timer; |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
// likely = optimize for small requests |
|
127
|
559
|
100
|
|
|
|
|
if (likely(c->receiving <= RECEIVE_HEADERS)) { |
|
128
|
545
|
|
|
|
|
|
int ret = try_parse_http(c, (size_t)got_n); |
|
129
|
545
|
100
|
|
|
|
|
if (ret == -1) goto try_read_bad; |
|
130
|
|
|
|
|
|
|
#ifdef TCP_DEFER_ACCEPT |
|
131
|
529
|
100
|
|
|
|
|
if (ret == -2) goto try_read_again_reset_timer; |
|
132
|
|
|
|
|
|
|
#else |
|
133
|
|
|
|
|
|
|
if (ret == -2) { |
|
134
|
|
|
|
|
|
|
if (c->cached_is_tcp) goto try_read_again; |
|
135
|
|
|
|
|
|
|
else goto try_read_again_reset_timer; |
|
136
|
|
|
|
|
|
|
} |
|
137
|
|
|
|
|
|
|
#endif |
|
138
|
|
|
|
|
|
|
|
|
139
|
507
|
100
|
|
|
|
|
if (process_request_headers(c, ret)) |
|
140
|
15
|
|
|
|
|
|
goto try_read_again_reset_timer; |
|
141
|
|
|
|
|
|
|
else |
|
142
|
492
|
|
|
|
|
|
goto dont_read_again; |
|
143
|
|
|
|
|
|
|
} |
|
144
|
14
|
100
|
|
|
|
|
else if (likely(c->receiving == RECEIVE_BODY)) { |
|
145
|
10
|
|
|
|
|
|
c->received_cl += got_n; |
|
146
|
10
|
100
|
|
|
|
|
if (c->received_cl < c->expected_cl) |
|
147
|
1
|
|
|
|
|
|
goto try_read_again_reset_timer; |
|
148
|
|
|
|
|
|
|
// body is complete |
|
149
|
9
|
|
|
|
|
|
sched_request_callback(c); |
|
150
|
9
|
|
|
|
|
|
goto dont_read_again; |
|
151
|
|
|
|
|
|
|
} |
|
152
|
4
|
50
|
|
|
|
|
else if (c->receiving == RECEIVE_CHUNKED) { |
|
153
|
|
|
|
|
|
|
// Try to parse chunked data |
|
154
|
4
|
|
|
|
|
|
int ret = try_parse_chunked(c); |
|
155
|
4
|
50
|
|
|
|
|
if (ret == 1) |
|
156
|
0
|
|
|
|
|
|
goto try_read_again_reset_timer; |
|
157
|
4
|
50
|
|
|
|
|
if (ret == -1) { |
|
158
|
0
|
|
|
|
|
|
respond_with_server_error(c, "Malformed chunked encoding\n", 400); |
|
159
|
0
|
|
|
|
|
|
goto dont_read_again; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
// chunked body is complete |
|
162
|
4
|
|
|
|
|
|
sched_request_callback(c); |
|
163
|
4
|
|
|
|
|
|
goto dont_read_again; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
0
|
0
|
|
|
|
|
else if (c->receiving == RECEIVE_STREAMING) { |
|
166
|
|
|
|
|
|
|
// Streaming body read with poll_read_cb |
|
167
|
0
|
|
|
|
|
|
c->received_cl += got_n; |
|
168
|
0
|
0
|
|
|
|
|
if (c->poll_read_cb) { |
|
169
|
0
|
|
|
|
|
|
call_poll_callback(c, 0); // 0 = read callback |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
// Callback may have closed/errored — check before re-arming |
|
172
|
0
|
0
|
|
|
|
|
if (c->receiving >= RECEIVE_SHUTDOWN) |
|
173
|
0
|
|
|
|
|
|
goto dont_read_again; |
|
174
|
|
|
|
|
|
|
// Check if body is complete (if Content-Length was specified) |
|
175
|
0
|
0
|
|
|
|
|
if (c->expected_cl > 0 && c->received_cl >= c->expected_cl) { |
|
|
|
0
|
|
|
|
|
|
|
176
|
0
|
|
|
|
|
|
goto dont_read_again; |
|
177
|
|
|
|
|
|
|
} |
|
178
|
0
|
|
|
|
|
|
goto try_read_again_reset_timer; |
|
179
|
|
|
|
|
|
|
} |
|
180
|
|
|
|
|
|
|
else { |
|
181
|
0
|
|
|
|
|
|
trouble("unknown read state %d %d", w->fd, c->receiving); |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
// fallthrough: |
|
185
|
28
|
|
|
|
|
|
try_read_error: |
|
186
|
|
|
|
|
|
|
trace("READ ERROR %d, refcnt=%d\n", w->fd, SvREFCNT(c->self)); |
|
187
|
28
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_SHUTDOWN); |
|
188
|
28
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
189
|
28
|
|
|
|
|
|
stop_all_watchers(c); |
|
190
|
28
|
|
|
|
|
|
goto try_read_cleanup; |
|
191
|
|
|
|
|
|
|
|
|
192
|
16
|
|
|
|
|
|
try_read_bad: |
|
193
|
|
|
|
|
|
|
trace("bad request %d\n", w->fd); |
|
194
|
16
|
|
|
|
|
|
respond_with_server_error(c, "Malformed request\n", 400); |
|
195
|
|
|
|
|
|
|
// fallthrough (respond_with_server_error sets is_keepalive=0): |
|
196
|
613
|
|
|
|
|
|
dont_read_again: |
|
197
|
|
|
|
|
|
|
trace("done reading %d\n", w->fd); |
|
198
|
613
|
|
|
|
|
|
finish_receiving(c); |
|
199
|
613
|
|
|
|
|
|
goto try_read_cleanup; |
|
200
|
|
|
|
|
|
|
|
|
201
|
38
|
|
|
|
|
|
try_read_again_reset_timer: |
|
202
|
|
|
|
|
|
|
trace("(reset read timer) %d\n", w->fd); |
|
203
|
38
|
|
|
|
|
|
restart_read_timer(c); |
|
204
|
|
|
|
|
|
|
// fallthrough: |
|
205
|
38
|
|
|
|
|
|
try_read_again: |
|
206
|
|
|
|
|
|
|
trace("read again %d\n", w->fd); |
|
207
|
38
|
|
|
|
|
|
start_read_watcher(c); |
|
208
|
|
|
|
|
|
|
|
|
209
|
679
|
|
|
|
|
|
try_read_cleanup: |
|
210
|
679
|
|
|
|
|
|
SvREFCNT_dec(c->self); |
|
211
|
679
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
static void |
|
214
|
4
|
|
|
|
|
|
conn_read_timeout (EV_P_ ev_timer *w, int revents) |
|
215
|
|
|
|
|
|
|
{ |
|
216
|
4
|
|
|
|
|
|
dCONN; |
|
217
|
4
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); |
|
218
|
|
|
|
|
|
|
|
|
219
|
4
|
50
|
|
|
|
|
if (unlikely(!(revents & EV_TIMER) || c->receiving == RECEIVE_SHUTDOWN)) { |
|
|
|
50
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
// if there's no EV_TIMER then EV has stopped it on an error |
|
221
|
0
|
0
|
|
|
|
|
if (revents & EV_ERROR) |
|
222
|
0
|
|
|
|
|
|
trouble("EV error on read timer, fd=%d revents=0x%08x\n", |
|
223
|
|
|
|
|
|
|
c->fd,revents); |
|
224
|
0
|
|
|
|
|
|
goto read_timeout_cleanup; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
trace("read timeout %d\n", c->fd); |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
230
|
|
|
|
|
|
|
if (c->h2_session) { |
|
231
|
|
|
|
|
|
|
/* H2 parent connection idle timeout: send GOAWAY and close. |
|
232
|
|
|
|
|
|
|
* Cannot use respond_with_server_error() which writes HTTP/1.1. */ |
|
233
|
|
|
|
|
|
|
trace("H2 idle timeout fd=%d\n", c->fd); |
|
234
|
|
|
|
|
|
|
if (!c->h2_goaway_sent) { |
|
235
|
|
|
|
|
|
|
nghttp2_submit_goaway(c->h2_session, NGHTTP2_FLAG_NONE, |
|
236
|
|
|
|
|
|
|
nghttp2_session_get_last_proc_stream_id(c->h2_session), |
|
237
|
|
|
|
|
|
|
NGHTTP2_NO_ERROR, NULL, 0); |
|
238
|
|
|
|
|
|
|
c->h2_goaway_sent = 1; |
|
239
|
|
|
|
|
|
|
feer_h2_session_send(c); |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
stop_all_watchers(c); |
|
242
|
|
|
|
|
|
|
safe_close_conn(c, "H2 idle timeout"); |
|
243
|
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
244
|
|
|
|
|
|
|
goto read_timeout_cleanup; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
#endif |
|
247
|
|
|
|
|
|
|
|
|
248
|
7
|
50
|
|
|
|
|
if (likely(c->responding == RESPOND_NOT_STARTED) && c->receiving >= RECEIVE_HEADERS) { |
|
|
|
100
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
250
|
3
|
50
|
|
|
|
|
if (c->tls && !c->tls_handshake_done) { |
|
|
|
0
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
// TLS handshake never completed — can't send HTTP response |
|
252
|
|
|
|
|
|
|
// (ptls_send asserts enc.aead != NULL). Just close. |
|
253
|
0
|
|
|
|
|
|
stop_all_watchers(c); |
|
254
|
0
|
|
|
|
|
|
safe_close_conn(c, "TLS handshake timeout (read)"); |
|
255
|
0
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
256
|
0
|
|
|
|
|
|
goto read_timeout_cleanup; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
#endif |
|
259
|
|
|
|
|
|
|
const char *msg; |
|
260
|
3
|
50
|
|
|
|
|
if (c->receiving == RECEIVE_PROXY_HEADER) { |
|
261
|
0
|
|
|
|
|
|
msg = "PROXY protocol header timeout."; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
3
|
100
|
|
|
|
|
else if (c->receiving == RECEIVE_HEADERS) { |
|
264
|
2
|
|
|
|
|
|
msg = "Headers took too long."; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
else { |
|
267
|
1
|
|
|
|
|
|
msg = "Timeout reading body."; |
|
268
|
|
|
|
|
|
|
} |
|
269
|
3
|
|
|
|
|
|
respond_with_server_error(c, msg, 408); |
|
270
|
|
|
|
|
|
|
} else { |
|
271
|
|
|
|
|
|
|
trace("read timeout in keepalive conn: %d\n", c->fd); |
|
272
|
1
|
|
|
|
|
|
stop_all_watchers(c); |
|
273
|
1
|
|
|
|
|
|
safe_close_conn(c, "close at read timeout"); |
|
274
|
1
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
275
|
1
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_SHUTDOWN); |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
|
|
278
|
4
|
|
|
|
|
|
read_timeout_cleanup: |
|
279
|
4
|
|
|
|
|
|
SvREFCNT_dec(c->self); |
|
280
|
4
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
// Slowloris protection: non-resetting deadline for header completion |
|
283
|
|
|
|
|
|
|
static void |
|
284
|
8
|
|
|
|
|
|
conn_header_timeout (EV_P_ ev_timer *w, int revents) |
|
285
|
|
|
|
|
|
|
{ |
|
286
|
8
|
|
|
|
|
|
dCONN; |
|
287
|
8
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); |
|
288
|
|
|
|
|
|
|
|
|
289
|
8
|
50
|
|
|
|
|
if (unlikely(!(revents & EV_TIMER) || c->receiving == RECEIVE_SHUTDOWN)) { |
|
|
|
50
|
|
|
|
|
|
|
290
|
0
|
0
|
|
|
|
|
if (revents & EV_ERROR) |
|
291
|
0
|
|
|
|
|
|
trouble("EV error on header timer, fd=%d revents=0x%08x\n", |
|
292
|
|
|
|
|
|
|
c->fd, revents); |
|
293
|
0
|
|
|
|
|
|
goto header_timeout_cleanup; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
// Only trigger if still receiving headers (including PROXY protocol phase) |
|
297
|
8
|
50
|
|
|
|
|
if ((c->receiving == RECEIVE_HEADERS || c->receiving == RECEIVE_PROXY_HEADER) |
|
|
|
0
|
|
|
|
|
|
|
298
|
8
|
50
|
|
|
|
|
&& c->responding == RESPOND_NOT_STARTED) { |
|
299
|
|
|
|
|
|
|
trace("header deadline timeout %d (Slowloris protection)\n", c->fd); |
|
300
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
301
|
8
|
100
|
|
|
|
|
if (c->tls && !c->tls_handshake_done) { |
|
|
|
50
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
// TLS handshake never completed — can't send HTTP response |
|
303
|
|
|
|
|
|
|
// (ptls_send asserts enc.aead != NULL). Just close. |
|
304
|
4
|
|
|
|
|
|
stop_all_watchers(c); |
|
305
|
4
|
|
|
|
|
|
safe_close_conn(c, "TLS handshake timeout"); |
|
306
|
4
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
307
|
|
|
|
|
|
|
} else |
|
308
|
|
|
|
|
|
|
#endif |
|
309
|
|
|
|
|
|
|
{ |
|
310
|
4
|
|
|
|
|
|
respond_with_server_error(c, "Header timeout (possible Slowloris attack)\n", 408); |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
0
|
|
|
|
|
|
header_timeout_cleanup: |
|
315
|
|
|
|
|
|
|
// One-shot timer: libev already stopped it before invoking this callback. |
|
316
|
8
|
|
|
|
|
|
SvREFCNT_dec(c->self); // balances timer start (setup_accepted_conn or keepalive) |
|
317
|
8
|
|
|
|
|
|
SvREFCNT_dec(c->self); // balances callback protection at top of this function |
|
318
|
8
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
static void |
|
321
|
0
|
|
|
|
|
|
conn_write_timeout (EV_P_ ev_timer *w, int revents) |
|
322
|
|
|
|
|
|
|
{ |
|
323
|
0
|
|
|
|
|
|
dCONN; |
|
324
|
0
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); |
|
325
|
|
|
|
|
|
|
|
|
326
|
0
|
0
|
|
|
|
|
if (unlikely(!(revents & EV_TIMER) || c->responding == RESPOND_SHUTDOWN)) { |
|
|
|
0
|
|
|
|
|
|
|
327
|
0
|
0
|
|
|
|
|
if (revents & EV_ERROR) |
|
328
|
0
|
|
|
|
|
|
trouble("EV error on write timer, fd=%d revents=0x%08x\n", |
|
329
|
|
|
|
|
|
|
c->fd, revents); |
|
330
|
0
|
|
|
|
|
|
goto write_timeout_cleanup; |
|
331
|
|
|
|
|
|
|
} |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
trace("write timeout %d\n", c->fd); |
|
334
|
0
|
|
|
|
|
|
stop_all_watchers(c); |
|
335
|
0
|
|
|
|
|
|
safe_close_conn(c, "write timeout"); |
|
336
|
0
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
337
|
0
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_SHUTDOWN); |
|
338
|
|
|
|
|
|
|
|
|
339
|
0
|
|
|
|
|
|
write_timeout_cleanup: |
|
340
|
0
|
|
|
|
|
|
SvREFCNT_dec(c->self); |
|
341
|
0
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
// Helper to set up a newly accepted connection |
|
344
|
|
|
|
|
|
|
// Returns 0 on success, -1 on error (fd already closed on error) |
|
345
|
|
|
|
|
|
|
static int |
|
346
|
589
|
|
|
|
|
|
setup_accepted_conn(EV_P_ int fd, struct sockaddr *sa, socklen_t sa_len, |
|
347
|
|
|
|
|
|
|
struct feer_server *srvr, struct feer_listen *lsnr) |
|
348
|
|
|
|
|
|
|
{ |
|
349
|
589
|
50
|
|
|
|
|
if (unlikely(prep_socket(fd, lsnr->is_tcp))) { |
|
350
|
0
|
|
|
|
|
|
trouble("prep_socket failed for fd=%d: %s\n", fd, strerror(errno)); |
|
351
|
0
|
0
|
|
|
|
|
if (unlikely(close(fd) < 0)) |
|
352
|
0
|
|
|
|
|
|
trouble("close(prep_socket error) fd=%d: %s\n", fd, strerror(errno)); |
|
353
|
0
|
|
|
|
|
|
return -1; |
|
354
|
|
|
|
|
|
|
} |
|
355
|
|
|
|
|
|
|
|
|
356
|
589
|
|
|
|
|
|
struct feer_conn *c = new_feer_conn(EV_A, fd, sa, sa_len, srvr, lsnr); |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
// Slowloris protection: start non-resetting header deadline timer |
|
359
|
589
|
100
|
|
|
|
|
if (srvr->header_timeout > 0.0) { |
|
360
|
588
|
|
|
|
|
|
ev_timer_set(&c->header_ev_timer, srvr->header_timeout, 0.0); // one-shot |
|
361
|
588
|
|
|
|
|
|
ev_timer_start(feersum_ev_loop, &c->header_ev_timer); |
|
362
|
588
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); |
|
363
|
|
|
|
|
|
|
trace("started header deadline timer %d (%.1fs)\n", c->fd, srvr->header_timeout); |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
#ifdef TCP_DEFER_ACCEPT |
|
367
|
|
|
|
|
|
|
// With TCP_DEFER_ACCEPT, data is already available |
|
368
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
369
|
589
|
100
|
|
|
|
|
if (lsnr->tls_ctx_ref) { |
|
370
|
|
|
|
|
|
|
// TLS: don't try immediate read with deferred accept, just start watcher. |
|
371
|
|
|
|
|
|
|
// The TLS handshake needs proper event-driven I/O. |
|
372
|
59
|
|
|
|
|
|
start_read_watcher(c); |
|
373
|
|
|
|
|
|
|
} else |
|
374
|
|
|
|
|
|
|
#endif |
|
375
|
|
|
|
|
|
|
{ |
|
376
|
530
|
|
|
|
|
|
try_conn_read(EV_A, &c->read_ev_io, EV_READ); |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
assert(SvREFCNT(c->self) <= (srvr->header_timeout > 0.0 ? 4 : 3)); |
|
379
|
|
|
|
|
|
|
#else |
|
380
|
|
|
|
|
|
|
if (lsnr->is_tcp) { |
|
381
|
|
|
|
|
|
|
start_read_watcher(c); |
|
382
|
|
|
|
|
|
|
restart_read_timer(c); |
|
383
|
|
|
|
|
|
|
assert(SvREFCNT(c->self) == (srvr->header_timeout > 0.0 ? 4 : 3)); |
|
384
|
|
|
|
|
|
|
} else { |
|
385
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
386
|
|
|
|
|
|
|
if (lsnr->tls_ctx_ref) |
|
387
|
|
|
|
|
|
|
try_tls_conn_read(EV_A, &c->read_ev_io, EV_READ); |
|
388
|
|
|
|
|
|
|
else |
|
389
|
|
|
|
|
|
|
#endif |
|
390
|
|
|
|
|
|
|
try_conn_read(EV_A, &c->read_ev_io, EV_READ); |
|
391
|
|
|
|
|
|
|
assert(SvREFCNT(c->self) <= (srvr->header_timeout > 0.0 ? 4 : 3)); |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
#endif |
|
394
|
589
|
|
|
|
|
|
SvREFCNT_dec(c->self); |
|
395
|
589
|
|
|
|
|
|
return 0; |
|
396
|
|
|
|
|
|
|
} |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
static void |
|
399
|
0
|
|
|
|
|
|
emfile_retry_cb (EV_P_ ev_timer *w, int revents) |
|
400
|
|
|
|
|
|
|
{ |
|
401
|
0
|
|
|
|
|
|
struct feer_listen *lsnr = (struct feer_listen *)w->data; |
|
402
|
0
|
0
|
|
|
|
|
if (lsnr->server->shutting_down) return; |
|
403
|
|
|
|
|
|
|
trace("EMFILE retry: resuming accept on fd=%d\n", lsnr->fd); |
|
404
|
0
|
|
|
|
|
|
lsnr->paused = 0; |
|
405
|
0
|
|
|
|
|
|
ev_io_start(EV_A, &lsnr->accept_w); |
|
406
|
|
|
|
|
|
|
} |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
/* |
|
409
|
|
|
|
|
|
|
* Accept a single connection from the listen socket. |
|
410
|
|
|
|
|
|
|
* Returns: 1 = continue accepting, 0 = stop (EAGAIN/error/limit) |
|
411
|
|
|
|
|
|
|
*/ |
|
412
|
|
|
|
|
|
|
static int |
|
413
|
1108
|
|
|
|
|
|
try_accept_one(EV_P_ struct feer_listen *lsnr, struct feer_server *srvr) |
|
414
|
|
|
|
|
|
|
{ |
|
415
|
|
|
|
|
|
|
struct sockaddr_storage sa_buf; |
|
416
|
1108
|
|
|
|
|
|
socklen_t sa_len = sizeof(struct sockaddr_storage); |
|
417
|
1108
|
|
|
|
|
|
errno = 0; |
|
418
|
|
|
|
|
|
|
#ifdef HAS_ACCEPT4 |
|
419
|
1108
|
|
|
|
|
|
int fd = accept4(lsnr->fd, (struct sockaddr *)&sa_buf, &sa_len, SOCK_CLOEXEC|SOCK_NONBLOCK); |
|
420
|
|
|
|
|
|
|
#else |
|
421
|
|
|
|
|
|
|
int fd = accept(lsnr->fd, (struct sockaddr *)&sa_buf, &sa_len); |
|
422
|
|
|
|
|
|
|
#endif |
|
423
|
|
|
|
|
|
|
trace("accepted fd=%d, errno=%d\n", fd, errno); |
|
424
|
1108
|
100
|
|
|
|
|
if (fd == -1) { |
|
425
|
518
|
50
|
|
|
|
|
if (errno == EINTR) return 1; |
|
426
|
518
|
50
|
|
|
|
|
if (errno == EMFILE || errno == ENFILE) { |
|
|
|
50
|
|
|
|
|
|
|
427
|
0
|
|
|
|
|
|
trouble("accept: %s — pausing accept for 1s on fd=%d\n", |
|
428
|
|
|
|
|
|
|
strerror(errno), lsnr->fd); |
|
429
|
0
|
|
|
|
|
|
ev_io_stop(EV_A, &lsnr->accept_w); |
|
430
|
0
|
|
|
|
|
|
lsnr->paused = 1; |
|
431
|
0
|
|
|
|
|
|
ev_timer_set(&lsnr->emfile_w, 1.0, 0.0); |
|
432
|
0
|
|
|
|
|
|
ev_timer_start(EV_A, &lsnr->emfile_w); |
|
433
|
|
|
|
|
|
|
} |
|
434
|
518
|
|
|
|
|
|
return 0; |
|
435
|
|
|
|
|
|
|
} |
|
436
|
|
|
|
|
|
|
|
|
437
|
590
|
100
|
|
|
|
|
if (srvr->max_connections > 0 && srvr->active_conns >= srvr->max_connections) { |
|
|
|
100
|
|
|
|
|
|
|
438
|
2
|
100
|
|
|
|
|
if (!feer_server_recycle_idle_conn(srvr)) { |
|
439
|
|
|
|
|
|
|
trace("max_connections limit reached (%d), rejecting fd=%d\n", |
|
440
|
|
|
|
|
|
|
srvr->max_connections, fd); |
|
441
|
1
|
|
|
|
|
|
close(fd); |
|
442
|
1
|
|
|
|
|
|
return 0; |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
} |
|
445
|
|
|
|
|
|
|
|
|
446
|
589
|
|
|
|
|
|
setup_accepted_conn(EV_A, fd, (struct sockaddr *)&sa_buf, sa_len, srvr, lsnr); |
|
447
|
589
|
|
|
|
|
|
return 1; |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
#ifdef __linux__ |
|
451
|
|
|
|
|
|
|
static void |
|
452
|
0
|
|
|
|
|
|
accept_epoll_cb (EV_P_ ev_io *w, int revents) |
|
453
|
|
|
|
|
|
|
{ |
|
454
|
0
|
|
|
|
|
|
struct feer_listen *lsnr = (struct feer_listen *)w->data; |
|
455
|
0
|
|
|
|
|
|
struct feer_server *srvr = lsnr->server; |
|
456
|
|
|
|
|
|
|
struct epoll_event events[1]; |
|
457
|
|
|
|
|
|
|
|
|
458
|
0
|
0
|
|
|
|
|
if (unlikely(srvr->shutting_down)) { |
|
459
|
0
|
|
|
|
|
|
ev_io_stop(EV_A, w); |
|
460
|
0
|
|
|
|
|
|
return; |
|
461
|
|
|
|
|
|
|
} |
|
462
|
|
|
|
|
|
|
|
|
463
|
0
|
0
|
|
|
|
|
if (unlikely(revents & EV_ERROR)) { |
|
464
|
0
|
|
|
|
|
|
trouble("EV error in accept_epoll_cb, fd=%d, revents=0x%08x\n", w->fd, revents); |
|
465
|
0
|
|
|
|
|
|
ev_break(EV_A, EVBREAK_ALL); |
|
466
|
0
|
|
|
|
|
|
return; |
|
467
|
|
|
|
|
|
|
} |
|
468
|
|
|
|
|
|
|
|
|
469
|
0
|
|
|
|
|
|
int accept_count = 0; |
|
470
|
0
|
0
|
|
|
|
|
while (accept_count++ < srvr->max_accept_per_loop) { |
|
471
|
0
|
0
|
|
|
|
|
if (epoll_wait(lsnr->epoll_fd, events, 1, 0) <= 0) break; |
|
472
|
0
|
0
|
|
|
|
|
if (!try_accept_one(EV_A, lsnr, srvr)) break; |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
} |
|
475
|
|
|
|
|
|
|
#endif |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
static void |
|
478
|
519
|
|
|
|
|
|
accept_cb (EV_P_ ev_io *w, int revents) |
|
479
|
|
|
|
|
|
|
{ |
|
480
|
519
|
|
|
|
|
|
struct feer_listen *lsnr = (struct feer_listen *)w->data; |
|
481
|
519
|
|
|
|
|
|
struct feer_server *srvr = lsnr->server; |
|
482
|
|
|
|
|
|
|
|
|
483
|
519
|
50
|
|
|
|
|
if (unlikely(srvr->shutting_down)) { |
|
484
|
0
|
|
|
|
|
|
ev_io_stop(EV_A, w); |
|
485
|
0
|
|
|
|
|
|
return; |
|
486
|
|
|
|
|
|
|
} |
|
487
|
|
|
|
|
|
|
|
|
488
|
519
|
50
|
|
|
|
|
if (unlikely(revents & EV_ERROR)) { |
|
489
|
0
|
|
|
|
|
|
trouble("EV error in accept_cb, fd=%d, revents=0x%08x\n",w->fd,revents); |
|
490
|
0
|
|
|
|
|
|
ev_break(EV_A, EVBREAK_ALL); |
|
491
|
0
|
|
|
|
|
|
return; |
|
492
|
|
|
|
|
|
|
} |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
trace2("accept! revents=0x%08x\n", revents); |
|
495
|
|
|
|
|
|
|
|
|
496
|
519
|
|
|
|
|
|
int accept_count = 0; |
|
497
|
1108
|
50
|
|
|
|
|
while (accept_count++ < srvr->max_accept_per_loop) { |
|
498
|
1108
|
100
|
|
|
|
|
if (!try_accept_one(EV_A, lsnr, srvr)) break; |
|
499
|
|
|
|
|
|
|
} |
|
500
|
|
|
|
|
|
|
} |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
// Helper to set up the accept watcher, with optional EPOLLEXCLUSIVE on Linux |
|
503
|
|
|
|
|
|
|
static void |
|
504
|
191
|
|
|
|
|
|
setup_accept_watcher(struct feer_listen *lsnr, int listen_fd) |
|
505
|
|
|
|
|
|
|
{ |
|
506
|
191
|
|
|
|
|
|
struct feer_server *srvr = lsnr->server; |
|
507
|
|
|
|
|
|
|
#if defined(__linux__) && defined(EPOLLEXCLUSIVE) |
|
508
|
191
|
100
|
|
|
|
|
if (srvr->use_epoll_exclusive) { |
|
509
|
|
|
|
|
|
|
// Create a separate epoll fd for the accept socket with EPOLLEXCLUSIVE |
|
510
|
|
|
|
|
|
|
// This avoids thundering herd in prefork without patching libev |
|
511
|
|
|
|
|
|
|
struct epoll_event ev; |
|
512
|
2
|
|
|
|
|
|
lsnr->epoll_fd = epoll_create1(EPOLL_CLOEXEC); |
|
513
|
2
|
50
|
|
|
|
|
if (lsnr->epoll_fd < 0) { |
|
514
|
0
|
|
|
|
|
|
trouble("epoll_create1 for accept: %s\n", strerror(errno)); |
|
515
|
0
|
|
|
|
|
|
croak("Failed to create accept epoll fd"); |
|
516
|
|
|
|
|
|
|
} |
|
517
|
|
|
|
|
|
|
|
|
518
|
2
|
|
|
|
|
|
ev.events = EPOLLIN | EPOLLEXCLUSIVE; |
|
519
|
2
|
|
|
|
|
|
ev.data.fd = listen_fd; |
|
520
|
2
|
50
|
|
|
|
|
if (epoll_ctl(lsnr->epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) < 0) { |
|
521
|
0
|
|
|
|
|
|
trouble("epoll_ctl EPOLL_CTL_ADD for accept fd=%d: %s\n", listen_fd, strerror(errno)); |
|
522
|
0
|
0
|
|
|
|
|
if (unlikely(close(lsnr->epoll_fd) < 0)) |
|
523
|
0
|
|
|
|
|
|
trouble("close(lsnr->epoll_fd) fd=%d: %s\n", lsnr->epoll_fd, strerror(errno)); |
|
524
|
0
|
|
|
|
|
|
lsnr->epoll_fd = -1; |
|
525
|
0
|
|
|
|
|
|
croak("Failed to add listen socket to accept epoll"); |
|
526
|
|
|
|
|
|
|
} |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
trace("created lsnr->epoll_fd=%d with EPOLLEXCLUSIVE for listen fd=%d\n", |
|
529
|
|
|
|
|
|
|
lsnr->epoll_fd, listen_fd); |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
// Watch the lsnr->epoll_fd instead of the listen socket directly |
|
532
|
|
|
|
|
|
|
// When lsnr->epoll_fd becomes readable, only THIS worker was selected |
|
533
|
2
|
|
|
|
|
|
ev_io_init(&lsnr->accept_w, accept_epoll_cb, lsnr->epoll_fd, EV_READ); |
|
534
|
|
|
|
|
|
|
} else { |
|
535
|
|
|
|
|
|
|
// Standard mode: watch listen socket directly |
|
536
|
189
|
|
|
|
|
|
ev_io_init(&lsnr->accept_w, accept_cb, listen_fd, EV_READ); |
|
537
|
|
|
|
|
|
|
} |
|
538
|
|
|
|
|
|
|
#else |
|
539
|
|
|
|
|
|
|
// Non-Linux or no EPOLLEXCLUSIVE: standard mode only |
|
540
|
|
|
|
|
|
|
ev_io_init(&lsnr->accept_w, accept_cb, listen_fd, EV_READ); |
|
541
|
|
|
|
|
|
|
#endif |
|
542
|
191
|
|
|
|
|
|
lsnr->accept_w.data = (void *)lsnr; |
|
543
|
191
|
|
|
|
|
|
ev_set_priority(&lsnr->accept_w, srvr->accept_priority); |
|
544
|
191
|
|
|
|
|
|
ev_init(&lsnr->emfile_w, emfile_retry_cb); |
|
545
|
191
|
|
|
|
|
|
lsnr->emfile_w.data = (void *)lsnr; |
|
546
|
191
|
|
|
|
|
|
} |
|
547
|
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
static void |
|
549
|
482
|
|
|
|
|
|
sched_request_callback (struct feer_conn *c) |
|
550
|
|
|
|
|
|
|
{ |
|
551
|
482
|
|
|
|
|
|
struct feer_server *server = c->server; |
|
552
|
|
|
|
|
|
|
if (FEERSUM_REQ_BODY_ENABLED()) { |
|
553
|
|
|
|
|
|
|
FEERSUM_REQ_BODY(c->fd, (size_t)(c->received_cl >= 0 ? c->received_cl : 0)); |
|
554
|
|
|
|
|
|
|
} |
|
555
|
|
|
|
|
|
|
trace("sched req callback: %d c=%p, head=%p\n", c->fd, c, server->request_ready_rinq); |
|
556
|
482
|
|
|
|
|
|
rinq_push(&server->request_ready_rinq, c); |
|
557
|
482
|
|
|
|
|
|
SvREFCNT_inc_void_NN(c->self); // for the rinq |
|
558
|
482
|
100
|
|
|
|
|
if (!ev_is_active(&server->ei)) { |
|
559
|
|
|
|
|
|
|
trace("starting idle watcher\n"); |
|
560
|
328
|
|
|
|
|
|
ev_idle_start(feersum_ev_loop, &server->ei); |
|
561
|
|
|
|
|
|
|
} |
|
562
|
482
|
|
|
|
|
|
} |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
// Parse chunked transfer encoding from rbuf |
|
565
|
|
|
|
|
|
|
// Returns: 1 if need more data, 0 if complete, -1 if parse error |
|
566
|
|
|
|
|
|
|
// Decodes chunks in-place: moves decoded data to beginning of rbuf |
|
567
|
|
|
|
|
|
|
// c->received_cl tracks decoded bytes, c->chunk_remaining tracks state (see CHUNK_STATE_*) |
|
568
|
|
|
|
|
|
|
static int |
|
569
|
70
|
|
|
|
|
|
try_parse_chunked (struct feer_conn *c) |
|
570
|
|
|
|
|
|
|
{ |
|
571
|
70
|
50
|
|
|
|
|
if (!c->rbuf) return 1; // need data |
|
572
|
|
|
|
|
|
|
|
|
573
|
70
|
|
|
|
|
|
char *buf = SvPVX(c->rbuf); |
|
574
|
70
|
|
|
|
|
|
STRLEN buf_len = SvCUR(c->rbuf); |
|
575
|
|
|
|
|
|
|
// received_cl tracks decoded position; should always be non-negative |
|
576
|
70
|
|
|
|
|
|
STRLEN read_pos = (c->received_cl >= 0) ? (STRLEN)c->received_cl : 0; |
|
577
|
70
|
|
|
|
|
|
STRLEN write_pos = read_pos; // decoded data position |
|
578
|
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
trace("try_parse_chunked fd=%d buf_len=%"Sz_uf" read_pos=%"Sz_uf" chunk_remaining=%"Ssz_df"\n", |
|
580
|
|
|
|
|
|
|
c->fd, (Sz)buf_len, (Sz)read_pos, (Ssz)c->chunk_remaining); |
|
581
|
|
|
|
|
|
|
|
|
582
|
5523
|
100
|
|
|
|
|
while (read_pos < buf_len) { |
|
583
|
5521
|
100
|
|
|
|
|
if (c->chunk_remaining == CHUNK_STATE_NEED_CRLF) { |
|
584
|
|
|
|
|
|
|
// Need CRLF after chunk data |
|
585
|
2
|
|
|
|
|
|
STRLEN remaining = buf_len - read_pos; |
|
586
|
2
|
50
|
|
|
|
|
if (remaining < 2) |
|
587
|
0
|
|
|
|
|
|
goto need_more; |
|
588
|
2
|
50
|
|
|
|
|
if (buf[read_pos] != '\r' || buf[read_pos+1] != '\n') { |
|
|
|
50
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
trace("chunked: missing CRLF after chunk data\n"); |
|
590
|
0
|
|
|
|
|
|
return -1; // parse error |
|
591
|
|
|
|
|
|
|
} |
|
592
|
2
|
|
|
|
|
|
read_pos += 2; |
|
593
|
2
|
|
|
|
|
|
c->chunk_remaining = CHUNK_STATE_PARSE_SIZE; // ready for next chunk size |
|
594
|
2
|
|
|
|
|
|
continue; |
|
595
|
|
|
|
|
|
|
} |
|
596
|
5519
|
100
|
|
|
|
|
else if (c->chunk_remaining == CHUNK_STATE_PARSE_SIZE) { |
|
597
|
|
|
|
|
|
|
// Parsing chunk size line: find CRLF |
|
598
|
2765
|
|
|
|
|
|
char *line_start = buf + read_pos; |
|
599
|
2765
|
|
|
|
|
|
char *line_end = NULL; |
|
600
|
2765
|
|
|
|
|
|
STRLEN remaining = buf_len - read_pos; |
|
601
|
|
|
|
|
|
|
STRLEN i; |
|
602
|
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
// Look for CRLF |
|
604
|
5853
|
50
|
|
|
|
|
for (i = 0; i + 1 < remaining; i++) { |
|
605
|
5853
|
100
|
|
|
|
|
if (line_start[i] == '\r' && line_start[i+1] == '\n') { |
|
|
|
50
|
|
|
|
|
|
|
606
|
2765
|
|
|
|
|
|
line_end = line_start + i; |
|
607
|
2765
|
|
|
|
|
|
break; |
|
608
|
|
|
|
|
|
|
} |
|
609
|
|
|
|
|
|
|
} |
|
610
|
|
|
|
|
|
|
|
|
611
|
2765
|
50
|
|
|
|
|
if (!line_end) { |
|
612
|
|
|
|
|
|
|
// Need more data for chunk size line |
|
613
|
|
|
|
|
|
|
trace("chunked: need more data for chunk size line\n"); |
|
614
|
0
|
|
|
|
|
|
goto need_more; |
|
615
|
|
|
|
|
|
|
} |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
// Parse hex chunk size (stop at ; for extensions) |
|
618
|
|
|
|
|
|
|
// Uses hex_decode_table for fast lookup (0-15 for valid, 0xFF for invalid) |
|
619
|
2765
|
|
|
|
|
|
UV chunk_size = 0; |
|
620
|
2765
|
|
|
|
|
|
int hex_digits = 0; |
|
621
|
2765
|
|
|
|
|
|
char *p = line_start; |
|
622
|
5545
|
100
|
|
|
|
|
while (p < line_end) { |
|
623
|
2796
|
|
|
|
|
|
unsigned char ch = (unsigned char)*p; |
|
624
|
2796
|
|
|
|
|
|
unsigned char val = hex_decode_table[ch]; |
|
625
|
2796
|
100
|
|
|
|
|
if (val != 0xFF) { |
|
626
|
2781
|
|
|
|
|
|
hex_digits++; |
|
627
|
|
|
|
|
|
|
// Check for potential overflow BEFORE shifting |
|
628
|
2781
|
100
|
|
|
|
|
if (chunk_size > (UV_MAX >> 4)) { |
|
629
|
|
|
|
|
|
|
trace("chunked: chunk size overflow\n"); |
|
630
|
1
|
|
|
|
|
|
return -1; |
|
631
|
|
|
|
|
|
|
} |
|
632
|
2780
|
|
|
|
|
|
chunk_size = (chunk_size << 4) | val; |
|
633
|
|
|
|
|
|
|
} |
|
634
|
15
|
100
|
|
|
|
|
else if (ch == ';' || ch == ' ' || ch == '\t') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
break; // chunk extension or whitespace, stop parsing |
|
636
|
|
|
|
|
|
|
} |
|
637
|
|
|
|
|
|
|
else { |
|
638
|
|
|
|
|
|
|
trace("chunked: invalid hex char '%c'\n", ch); |
|
639
|
6
|
|
|
|
|
|
return -1; // parse error |
|
640
|
|
|
|
|
|
|
} |
|
641
|
2780
|
|
|
|
|
|
p++; |
|
642
|
|
|
|
|
|
|
} |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
// Reject chunk size lines with no hex digits (e.g., ";ext\r\n") |
|
645
|
2758
|
100
|
|
|
|
|
if (hex_digits == 0) { |
|
646
|
|
|
|
|
|
|
trace("chunked: no hex digits in chunk size\n"); |
|
647
|
4
|
|
|
|
|
|
return -1; // parse error |
|
648
|
|
|
|
|
|
|
} |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
trace("chunked: parsed size=%"UVuf" at pos=%"Sz_uf"\n", |
|
651
|
|
|
|
|
|
|
chunk_size, (Sz)(line_start - buf)); |
|
652
|
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
// Move past the CRLF |
|
654
|
2754
|
|
|
|
|
|
read_pos = (line_end - buf) + 2; |
|
655
|
|
|
|
|
|
|
|
|
656
|
2754
|
100
|
|
|
|
|
if (chunk_size == 0) { |
|
657
|
|
|
|
|
|
|
// Final chunk - transition to trailer parsing state. |
|
658
|
|
|
|
|
|
|
// The c->chunk_remaining == 0 handler below does the actual |
|
659
|
|
|
|
|
|
|
// trailer scan (both for first entry and re-entry after needing |
|
660
|
|
|
|
|
|
|
// more data), so just set state and let the while loop continue. |
|
661
|
52
|
|
|
|
|
|
c->trailer_count = 0; |
|
662
|
52
|
|
|
|
|
|
c->chunk_remaining = 0; |
|
663
|
52
|
|
|
|
|
|
continue; |
|
664
|
|
|
|
|
|
|
} |
|
665
|
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
// Check cumulative body size (prevent overflow on 32-bit) |
|
667
|
|
|
|
|
|
|
// Split into two checks to avoid unsigned underflow when |
|
668
|
|
|
|
|
|
|
// chunk_size > MAX_BODY_LEN (which would wrap the subtraction) |
|
669
|
2702
|
50
|
|
|
|
|
if (unlikely(chunk_size > (UV)c->cached_max_body_len)) { |
|
670
|
|
|
|
|
|
|
trace("chunked: chunk too large %"UVuf"\n", chunk_size); |
|
671
|
0
|
|
|
|
|
|
return -1; // error |
|
672
|
|
|
|
|
|
|
} |
|
673
|
2702
|
50
|
|
|
|
|
if (unlikely(write_pos > (STRLEN)(c->cached_max_body_len - chunk_size))) { |
|
674
|
|
|
|
|
|
|
trace("chunked: body too large %"UVuf" + %"Sz_uf"\n", |
|
675
|
|
|
|
|
|
|
chunk_size, (Sz)write_pos); |
|
676
|
0
|
|
|
|
|
|
return -1; // error |
|
677
|
|
|
|
|
|
|
} |
|
678
|
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
// DoS protection: limit number of chunks |
|
680
|
2702
|
|
|
|
|
|
c->chunk_count++; |
|
681
|
2702
|
50
|
|
|
|
|
if (unlikely(c->chunk_count > MAX_CHUNK_COUNT)) { |
|
682
|
|
|
|
|
|
|
trace("chunked: too many chunks (%u)\n", c->chunk_count); |
|
683
|
0
|
|
|
|
|
|
return -1; // error |
|
684
|
|
|
|
|
|
|
} |
|
685
|
|
|
|
|
|
|
|
|
686
|
2702
|
50
|
|
|
|
|
if (chunk_size > (UV)SSIZE_MAX) return -1; |
|
687
|
2702
|
|
|
|
|
|
c->chunk_remaining = (ssize_t)chunk_size; |
|
688
|
|
|
|
|
|
|
} |
|
689
|
2754
|
100
|
|
|
|
|
else if (c->chunk_remaining == 0) { |
|
690
|
|
|
|
|
|
|
// We've seen the 0 chunk, looking for trailer end |
|
691
|
|
|
|
|
|
|
// (This handles the case where we return to this function) |
|
692
|
|
|
|
|
|
|
// Note: c->trailer_count was initialized when we first saw the 0-chunk |
|
693
|
52
|
|
|
|
|
|
STRLEN remaining = buf_len - read_pos; |
|
694
|
52
|
|
|
|
|
|
char *trailer_start = buf + read_pos; |
|
695
|
52
|
|
|
|
|
|
STRLEN i = 0; |
|
696
|
5794
|
50
|
|
|
|
|
while (i + 1 < remaining) { |
|
697
|
5794
|
100
|
|
|
|
|
if (trailer_start[i] == '\r' && trailer_start[i+1] == '\n') { |
|
|
|
50
|
|
|
|
|
|
|
698
|
317
|
100
|
|
|
|
|
if (i == 0) { |
|
699
|
|
|
|
|
|
|
// Empty line - done (chunk_remaining already 0) |
|
700
|
50
|
|
|
|
|
|
read_pos += 2; // skip terminating \r\n |
|
701
|
50
|
100
|
|
|
|
|
STRLEN pipelined = (read_pos < buf_len) ? buf_len - read_pos : 0; |
|
702
|
50
|
|
|
|
|
|
c->expected_cl = write_pos; |
|
703
|
50
|
|
|
|
|
|
c->received_cl = write_pos + pipelined; |
|
704
|
50
|
100
|
|
|
|
|
if (pipelined > 0) |
|
705
|
2
|
|
|
|
|
|
memmove(buf + write_pos, buf + read_pos, pipelined); |
|
706
|
50
|
|
|
|
|
|
SvCUR_set(c->rbuf, write_pos + pipelined); |
|
707
|
50
|
|
|
|
|
|
return 0; // complete |
|
708
|
|
|
|
|
|
|
} |
|
709
|
|
|
|
|
|
|
// Skip trailer header |
|
710
|
267
|
100
|
|
|
|
|
if (unlikely(++c->trailer_count > MAX_TRAILER_HEADERS)) { |
|
711
|
|
|
|
|
|
|
trace("chunked: too many trailer headers\n"); |
|
712
|
2
|
|
|
|
|
|
return -1; // error |
|
713
|
|
|
|
|
|
|
} |
|
714
|
265
|
|
|
|
|
|
read_pos += i + 2; |
|
715
|
265
|
|
|
|
|
|
remaining = buf_len - read_pos; |
|
716
|
265
|
|
|
|
|
|
trailer_start = buf + read_pos; |
|
717
|
265
|
|
|
|
|
|
i = 0; // restart from beginning |
|
718
|
265
|
|
|
|
|
|
continue; |
|
719
|
|
|
|
|
|
|
} |
|
720
|
5477
|
|
|
|
|
|
i++; |
|
721
|
|
|
|
|
|
|
} |
|
722
|
0
|
|
|
|
|
|
goto need_more; |
|
723
|
|
|
|
|
|
|
} |
|
724
|
|
|
|
|
|
|
else { |
|
725
|
|
|
|
|
|
|
// chunk_remaining > 0: copy chunk data |
|
726
|
2702
|
|
|
|
|
|
STRLEN remaining = buf_len - read_pos; |
|
727
|
2702
|
|
|
|
|
|
STRLEN to_copy = (STRLEN)c->chunk_remaining; |
|
728
|
2702
|
100
|
|
|
|
|
if (to_copy > remaining) |
|
729
|
1
|
|
|
|
|
|
to_copy = remaining; |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
// Move chunk data to write position (decode in place) |
|
732
|
2702
|
50
|
|
|
|
|
if (write_pos != read_pos && to_copy > 0) { |
|
|
|
50
|
|
|
|
|
|
|
733
|
2702
|
|
|
|
|
|
memmove(buf + write_pos, buf + read_pos, to_copy); |
|
734
|
|
|
|
|
|
|
} |
|
735
|
2702
|
|
|
|
|
|
write_pos += to_copy; |
|
736
|
2702
|
|
|
|
|
|
read_pos += to_copy; |
|
737
|
2702
|
|
|
|
|
|
c->chunk_remaining -= to_copy; |
|
738
|
2702
|
|
|
|
|
|
c->received_cl = write_pos; |
|
739
|
|
|
|
|
|
|
|
|
740
|
2702
|
100
|
|
|
|
|
if (c->chunk_remaining > 0) { |
|
741
|
|
|
|
|
|
|
// Need more data for this chunk |
|
742
|
1
|
|
|
|
|
|
goto need_more; |
|
743
|
|
|
|
|
|
|
} |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
// Chunk complete, need to consume trailing CRLF |
|
746
|
2701
|
|
|
|
|
|
remaining = buf_len - read_pos; |
|
747
|
2701
|
100
|
|
|
|
|
if (remaining < 2) { |
|
748
|
|
|
|
|
|
|
// Need CRLF |
|
749
|
2
|
|
|
|
|
|
c->chunk_remaining = CHUNK_STATE_NEED_CRLF; |
|
750
|
2
|
|
|
|
|
|
goto need_more; |
|
751
|
|
|
|
|
|
|
} |
|
752
|
2699
|
100
|
|
|
|
|
if (buf[read_pos] != '\r' || buf[read_pos+1] != '\n') { |
|
|
|
50
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
trace("chunked: missing CRLF after chunk data\n"); |
|
754
|
2
|
|
|
|
|
|
return -1; // parse error |
|
755
|
|
|
|
|
|
|
} |
|
756
|
2697
|
|
|
|
|
|
read_pos += 2; |
|
757
|
2697
|
|
|
|
|
|
c->chunk_remaining = CHUNK_STATE_PARSE_SIZE; // ready for next chunk size |
|
758
|
|
|
|
|
|
|
} |
|
759
|
|
|
|
|
|
|
} |
|
760
|
|
|
|
|
|
|
|
|
761
|
2
|
|
|
|
|
|
need_more: |
|
762
|
|
|
|
|
|
|
// Compact buffer: move unparsed data to after decoded data |
|
763
|
5
|
100
|
|
|
|
|
if (read_pos > write_pos) { |
|
764
|
3
|
50
|
|
|
|
|
if (read_pos < buf_len) { |
|
765
|
0
|
|
|
|
|
|
STRLEN unparsed = buf_len - read_pos; |
|
766
|
0
|
|
|
|
|
|
memmove(buf + write_pos, buf + read_pos, unparsed); |
|
767
|
0
|
|
|
|
|
|
SvCUR_set(c->rbuf, write_pos + unparsed); |
|
768
|
|
|
|
|
|
|
} else { |
|
769
|
3
|
|
|
|
|
|
SvCUR_set(c->rbuf, write_pos); |
|
770
|
|
|
|
|
|
|
} |
|
771
|
|
|
|
|
|
|
} |
|
772
|
|
|
|
|
|
|
// else: read_pos == write_pos, buffer is already compact, SvCUR unchanged |
|
773
|
5
|
|
|
|
|
|
c->received_cl = write_pos; |
|
774
|
5
|
|
|
|
|
|
return 1; // need more data |
|
775
|
|
|
|
|
|
|
} |
|
776
|
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
// the unlikely/likely annotations here are trying to optimize for GET first |
|
778
|
|
|
|
|
|
|
// and POST second. Other entity-body requests are third in line. |
|
779
|
|
|
|
|
|
|
static bool |
|
780
|
556
|
|
|
|
|
|
process_request_headers (struct feer_conn *c, int body_offset) |
|
781
|
|
|
|
|
|
|
{ |
|
782
|
|
|
|
|
|
|
int err_code; |
|
783
|
|
|
|
|
|
|
const char *err; |
|
784
|
556
|
|
|
|
|
|
struct feer_req *req = c->req; |
|
785
|
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
if (FEERSUM_REQ_NEW_ENABLED()) { |
|
787
|
|
|
|
|
|
|
char m[16], u[1024]; |
|
788
|
|
|
|
|
|
|
STRLEN m_len = (req->method_len < 15) ? req->method_len : 15; |
|
789
|
|
|
|
|
|
|
STRLEN u_len = (req->uri_len < 1023) ? req->uri_len : 1023; |
|
790
|
|
|
|
|
|
|
memcpy(m, req->method, m_len); m[m_len] = '\0'; |
|
791
|
|
|
|
|
|
|
memcpy(u, req->uri, u_len); u[u_len] = '\0'; |
|
792
|
|
|
|
|
|
|
FEERSUM_REQ_NEW(c->fd, m, u); |
|
793
|
|
|
|
|
|
|
} |
|
794
|
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
// Slowloris protection: headers complete, stop deadline timer |
|
796
|
556
|
|
|
|
|
|
stop_header_timer(c); |
|
797
|
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
trace("processing headers %d minor_version=%d\n",c->fd,req->minor_version); |
|
799
|
556
|
|
|
|
|
|
bool body_is_required = 0; |
|
800
|
556
|
|
|
|
|
|
bool next_req_follows = 0; |
|
801
|
556
|
|
|
|
|
|
bool got_content_length = 0; |
|
802
|
|
|
|
|
|
|
|
|
803
|
556
|
|
|
|
|
|
c->is_http11 = (req->minor_version == 1); |
|
804
|
556
|
100
|
|
|
|
|
c->is_keepalive = c->cached_keepalive_default && c->is_http11; |
|
|
|
100
|
|
|
|
|
|
|
805
|
556
|
|
|
|
|
|
c->expect_continue = 0; // reset for each request |
|
806
|
556
|
|
|
|
|
|
c->receive_chunked = 0; // reset for each request |
|
807
|
556
|
|
|
|
|
|
c->reqs++; |
|
808
|
|
|
|
|
|
|
|
|
809
|
556
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_BODY); |
|
810
|
556
|
|
|
|
|
|
c->expected_cl = 0; |
|
811
|
556
|
|
|
|
|
|
c->received_cl = 0; |
|
812
|
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
// Dispatch by method length first to minimize string comparisons |
|
814
|
556
|
|
|
|
|
|
switch (req->method_len) { |
|
815
|
393
|
|
|
|
|
|
case 3: |
|
816
|
393
|
100
|
|
|
|
|
if (likely(memcmp(req->method, "GET", 3) == 0)) { |
|
817
|
392
|
|
|
|
|
|
next_req_follows = 1; |
|
818
|
1
|
50
|
|
|
|
|
} else if (memcmp(req->method, "PUT", 3) == 0) { |
|
819
|
1
|
|
|
|
|
|
body_is_required = 1; |
|
820
|
|
|
|
|
|
|
} else { |
|
821
|
0
|
|
|
|
|
|
goto unsupported_method; |
|
822
|
|
|
|
|
|
|
} |
|
823
|
393
|
|
|
|
|
|
break; |
|
824
|
152
|
|
|
|
|
|
case 4: |
|
825
|
152
|
100
|
|
|
|
|
if (likely(memcmp(req->method, "POST", 4) == 0)) { |
|
826
|
151
|
|
|
|
|
|
body_is_required = 1; |
|
827
|
1
|
50
|
|
|
|
|
} else if (memcmp(req->method, "HEAD", 4) == 0) { |
|
828
|
1
|
|
|
|
|
|
next_req_follows = 1; |
|
829
|
|
|
|
|
|
|
} else { |
|
830
|
0
|
|
|
|
|
|
goto unsupported_method; |
|
831
|
|
|
|
|
|
|
} |
|
832
|
152
|
|
|
|
|
|
break; |
|
833
|
2
|
|
|
|
|
|
case 5: |
|
834
|
2
|
50
|
|
|
|
|
if (memcmp(req->method, "PATCH", 5) == 0) { |
|
835
|
2
|
|
|
|
|
|
body_is_required = 1; |
|
836
|
|
|
|
|
|
|
} else { |
|
837
|
0
|
|
|
|
|
|
goto unsupported_method; |
|
838
|
|
|
|
|
|
|
} |
|
839
|
2
|
|
|
|
|
|
break; |
|
840
|
2
|
|
|
|
|
|
case 6: |
|
841
|
2
|
100
|
|
|
|
|
if (memcmp(req->method, "DELETE", 6) == 0) { |
|
842
|
1
|
|
|
|
|
|
next_req_follows = 1; |
|
843
|
|
|
|
|
|
|
} else { |
|
844
|
1
|
|
|
|
|
|
goto unsupported_method; |
|
845
|
|
|
|
|
|
|
} |
|
846
|
1
|
|
|
|
|
|
break; |
|
847
|
1
|
|
|
|
|
|
case 7: |
|
848
|
1
|
50
|
|
|
|
|
if (memcmp(req->method, "OPTIONS", 7) == 0) { |
|
849
|
1
|
|
|
|
|
|
next_req_follows = 1; |
|
850
|
|
|
|
|
|
|
} else { |
|
851
|
0
|
|
|
|
|
|
goto unsupported_method; |
|
852
|
|
|
|
|
|
|
} |
|
853
|
1
|
|
|
|
|
|
break; |
|
854
|
|
|
|
|
|
|
default: |
|
855
|
7
|
|
|
|
|
|
unsupported_method: |
|
856
|
7
|
|
|
|
|
|
err = "Feersum doesn't support that method yet\n"; |
|
857
|
7
|
|
|
|
|
|
err_code = 405; |
|
858
|
7
|
|
|
|
|
|
goto got_bad_request; |
|
859
|
|
|
|
|
|
|
} |
|
860
|
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
// RFC 7230: URI length check (414 URI Too Long) |
|
862
|
549
|
100
|
|
|
|
|
if (unlikely(req->uri_len > c->cached_max_uri_len)) { |
|
863
|
3
|
|
|
|
|
|
err_code = 414; |
|
864
|
3
|
|
|
|
|
|
err = "URI Too Long\n"; |
|
865
|
3
|
|
|
|
|
|
goto got_bad_request; |
|
866
|
|
|
|
|
|
|
} |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
#if DEBUG >= 2 |
|
869
|
|
|
|
|
|
|
if (next_req_follows) |
|
870
|
|
|
|
|
|
|
trace2("next req follows fd=%d, boff=%d\n",c->fd,body_offset); |
|
871
|
|
|
|
|
|
|
if (body_is_required) |
|
872
|
|
|
|
|
|
|
trace2("body is required fd=%d, boff=%d\n",c->fd,body_offset); |
|
873
|
|
|
|
|
|
|
#endif |
|
874
|
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
// a body or follow-on data potentially follows the headers. Let feer_req |
|
876
|
|
|
|
|
|
|
// retain its pointers into rbuf and make a new scalar for more body data. |
|
877
|
|
|
|
|
|
|
STRLEN from_len; |
|
878
|
546
|
|
|
|
|
|
char *from = SvPV(c->rbuf,from_len); |
|
879
|
|
|
|
|
|
|
// Validate body_offset to prevent integer underflow |
|
880
|
|
|
|
|
|
|
// Check for negative first (phr_parse_request returns -1/-2 for errors) |
|
881
|
546
|
50
|
|
|
|
|
if (unlikely(body_offset < 0 || (STRLEN)body_offset > from_len)) { |
|
|
|
50
|
|
|
|
|
|
|
882
|
0
|
|
|
|
|
|
trouble("invalid body_offset %d > from_len %"Sz_uf" fd=%d\n", |
|
883
|
|
|
|
|
|
|
body_offset, (Sz)from_len, c->fd); |
|
884
|
0
|
|
|
|
|
|
respond_with_server_error(c, "Internal parser error\n", 500); |
|
885
|
0
|
|
|
|
|
|
return 0; |
|
886
|
|
|
|
|
|
|
} |
|
887
|
546
|
|
|
|
|
|
from += body_offset; |
|
888
|
546
|
|
|
|
|
|
STRLEN need = from_len - body_offset; |
|
889
|
|
|
|
|
|
|
trace("new rbuf for body %d need=%"Sz_uf"\n", c->fd, (Sz)need); |
|
890
|
546
|
100
|
|
|
|
|
SV *new_rbuf = newSVpvn(need > 0 ? from : "", need); |
|
891
|
|
|
|
|
|
|
|
|
892
|
546
|
|
|
|
|
|
req->buf = c->rbuf; |
|
893
|
546
|
|
|
|
|
|
c->rbuf = new_rbuf; |
|
894
|
546
|
|
|
|
|
|
SvCUR_set(req->buf, body_offset); |
|
895
|
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
// determine how much we need to read |
|
897
|
|
|
|
|
|
|
size_t i; |
|
898
|
546
|
|
|
|
|
|
UV expected = 0; |
|
899
|
546
|
|
|
|
|
|
bool got_host = 0; |
|
900
|
546
|
|
|
|
|
|
bool got_transfer_encoding = 0; |
|
901
|
1863
|
100
|
|
|
|
|
for (i=0; i < req->num_headers; i++) { |
|
902
|
1359
|
|
|
|
|
|
struct phr_header *hdr = &req->headers[i]; |
|
903
|
|
|
|
|
|
|
// RFC 7230: reject obsolete header line folding (obs-fold) |
|
904
|
1359
|
100
|
|
|
|
|
if (unlikely(!hdr->name)) { |
|
905
|
1
|
|
|
|
|
|
err_code = 400; |
|
906
|
1
|
|
|
|
|
|
err = "Obsolete header line folding not allowed\n"; |
|
907
|
1
|
|
|
|
|
|
goto got_bad_request; |
|
908
|
|
|
|
|
|
|
} |
|
909
|
|
|
|
|
|
|
// RFC 7231: reject header names that exceed our processing limit |
|
910
|
|
|
|
|
|
|
// Buffer is 5 + MAX_HEADER_NAME_LEN, so names up to MAX_HEADER_NAME_LEN fit |
|
911
|
1358
|
100
|
|
|
|
|
if (unlikely(hdr->name_len > MAX_HEADER_NAME_LEN)) { |
|
912
|
2
|
|
|
|
|
|
err_code = 431; |
|
913
|
2
|
|
|
|
|
|
err = "Header name too long\n"; |
|
914
|
2
|
|
|
|
|
|
goto got_bad_request; |
|
915
|
|
|
|
|
|
|
} |
|
916
|
1356
|
100
|
|
|
|
|
if (unlikely(hdr->name_len == 14 && |
|
|
|
50
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
str_case_eq_fixed("content-length", hdr->name, 14))) |
|
918
|
65
|
|
|
|
|
|
{ |
|
919
|
|
|
|
|
|
|
// RFC 7230 3.3.3: reject if Transfer-Encoding was already seen |
|
920
|
75
|
100
|
|
|
|
|
if (c->receive_chunked) { |
|
921
|
3
|
|
|
|
|
|
err_code = 400; |
|
922
|
3
|
|
|
|
|
|
err = "Content-Length not allowed with Transfer-Encoding\n"; |
|
923
|
10
|
|
|
|
|
|
goto got_bad_request; |
|
924
|
|
|
|
|
|
|
} |
|
925
|
72
|
|
|
|
|
|
UV new_expected = 0; |
|
926
|
72
|
|
|
|
|
|
int g = grok_number(hdr->value, hdr->value_len, &new_expected); |
|
927
|
72
|
100
|
|
|
|
|
if (likely(g == IS_NUMBER_IN_UV)) { |
|
928
|
70
|
100
|
|
|
|
|
if (unlikely(new_expected > (UV)c->cached_max_body_len)) { |
|
929
|
3
|
|
|
|
|
|
err_code = 413; |
|
930
|
3
|
|
|
|
|
|
err = "Content length exceeds maximum\n"; |
|
931
|
3
|
|
|
|
|
|
goto got_bad_request; |
|
932
|
|
|
|
|
|
|
} |
|
933
|
|
|
|
|
|
|
// RFC 7230: reject multiple Content-Length with different values |
|
934
|
67
|
100
|
|
|
|
|
if (got_content_length && new_expected != expected) { |
|
|
|
100
|
|
|
|
|
|
|
935
|
2
|
|
|
|
|
|
err_code = 400; |
|
936
|
2
|
|
|
|
|
|
err = "Multiple conflicting Content-Length headers\n"; |
|
937
|
2
|
|
|
|
|
|
goto got_bad_request; |
|
938
|
|
|
|
|
|
|
} |
|
939
|
65
|
|
|
|
|
|
expected = new_expected; |
|
940
|
65
|
|
|
|
|
|
got_content_length = 1; |
|
941
|
|
|
|
|
|
|
} |
|
942
|
|
|
|
|
|
|
else { |
|
943
|
2
|
|
|
|
|
|
err_code = 400; |
|
944
|
2
|
|
|
|
|
|
err = "Invalid Content-Length\n"; |
|
945
|
2
|
|
|
|
|
|
goto got_bad_request; |
|
946
|
|
|
|
|
|
|
} |
|
947
|
|
|
|
|
|
|
} |
|
948
|
1281
|
100
|
|
|
|
|
else if (unlikely(hdr->name_len == 10 && |
|
|
|
100
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
str_case_eq_fixed("connection", hdr->name, 10))) |
|
950
|
|
|
|
|
|
|
{ |
|
951
|
357
|
100
|
|
|
|
|
if (c->is_http11 && c->is_keepalive && |
|
|
|
100
|
|
|
|
|
|
|
952
|
74
|
100
|
|
|
|
|
hdr->value_len == 5 && str_case_eq_fixed("close", hdr->value, 5)) |
|
|
|
50
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
{ |
|
954
|
66
|
|
|
|
|
|
c->is_keepalive = 0; |
|
955
|
|
|
|
|
|
|
trace("setting conn %d to close after response\n", c->fd); |
|
956
|
|
|
|
|
|
|
} |
|
957
|
291
|
100
|
|
|
|
|
else if (!c->is_http11 && c->cached_keepalive_default && |
|
|
|
50
|
|
|
|
|
|
|
958
|
5
|
100
|
|
|
|
|
hdr->value_len == 10 && str_case_eq_fixed("keep-alive", hdr->value, 10)) |
|
|
|
50
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
{ |
|
960
|
1
|
|
|
|
|
|
c->is_keepalive = 1; |
|
961
|
|
|
|
|
|
|
trace("setting conn %d to keep after response\n", c->fd); |
|
962
|
|
|
|
|
|
|
} |
|
963
|
357
|
100
|
|
|
|
|
if (next_req_follows && c->receive_chunked && (!c->is_http11 || got_host)) break; |
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
} |
|
965
|
924
|
100
|
|
|
|
|
else if (unlikely(c->is_http11 && hdr->name_len == 6 && |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
str_case_eq_fixed("expect", hdr->name, 6))) |
|
967
|
|
|
|
|
|
|
{ |
|
968
|
|
|
|
|
|
|
// Check for "100-continue" value (case-insensitive) |
|
969
|
35
|
|
|
|
|
|
if (hdr->value_len == 12 && |
|
970
|
13
|
|
|
|
|
|
str_case_eq_fixed("100-continue", hdr->value, 12)) |
|
971
|
|
|
|
|
|
|
{ |
|
972
|
13
|
|
|
|
|
|
c->expect_continue = 1; |
|
973
|
|
|
|
|
|
|
trace("got Expect: 100-continue on fd=%d\n", c->fd); |
|
974
|
|
|
|
|
|
|
} |
|
975
|
|
|
|
|
|
|
else { |
|
976
|
|
|
|
|
|
|
// RFC 7231: unknown expectation, respond with 417 |
|
977
|
9
|
|
|
|
|
|
err_code = 417; |
|
978
|
9
|
|
|
|
|
|
err = "Expectation Failed\n"; |
|
979
|
9
|
|
|
|
|
|
goto got_bad_request; |
|
980
|
|
|
|
|
|
|
} |
|
981
|
|
|
|
|
|
|
} |
|
982
|
902
|
100
|
|
|
|
|
else if (unlikely(c->is_http11 && hdr->name_len == 17 && |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
str_case_eq_fixed("transfer-encoding", hdr->name, 17))) |
|
984
|
78
|
|
|
|
|
|
{ |
|
985
|
|
|
|
|
|
|
// RFC 7230 3.3.3: reject multiple Transfer-Encoding headers |
|
986
|
|
|
|
|
|
|
// to prevent request smuggling attacks |
|
987
|
98
|
100
|
|
|
|
|
if (got_transfer_encoding) { |
|
988
|
6
|
|
|
|
|
|
err_code = 400; |
|
989
|
6
|
|
|
|
|
|
err = "Multiple Transfer-Encoding headers not allowed\n"; |
|
990
|
6
|
|
|
|
|
|
goto got_bad_request; |
|
991
|
|
|
|
|
|
|
} |
|
992
|
92
|
|
|
|
|
|
got_transfer_encoding = 1; |
|
993
|
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
// RFC 7230: Accept "chunked" with optional extensions |
|
995
|
|
|
|
|
|
|
// Valid formats: "chunked", "chunked;ext=val", "chunked ; ext" |
|
996
|
90
|
100
|
|
|
|
|
bool is_chunked = (hdr->value_len >= 7 && |
|
997
|
182
|
100
|
|
|
|
|
str_case_eq_fixed("chunked", hdr->value, 7) && |
|
998
|
82
|
100
|
|
|
|
|
(hdr->value_len == 7 || |
|
999
|
3
|
100
|
|
|
|
|
hdr->value[7] == ';' || |
|
1000
|
1
|
50
|
|
|
|
|
hdr->value[7] == ' ' || |
|
1001
|
0
|
0
|
|
|
|
|
hdr->value[7] == '\t')); |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
// Also accept "identity" which means no encoding |
|
1004
|
97
|
|
|
|
|
|
bool is_identity = (hdr->value_len == 8 && |
|
1005
|
5
|
|
|
|
|
|
str_case_eq_fixed("identity", hdr->value, 8)); |
|
1006
|
|
|
|
|
|
|
|
|
1007
|
92
|
100
|
|
|
|
|
if (is_chunked) { |
|
1008
|
|
|
|
|
|
|
// RFC 7230 3.3.3: reject if Content-Length is also present |
|
1009
|
|
|
|
|
|
|
// This prevents request smuggling attacks |
|
1010
|
82
|
100
|
|
|
|
|
if (got_content_length) { |
|
1011
|
8
|
|
|
|
|
|
err_code = 400; |
|
1012
|
8
|
|
|
|
|
|
err = "Content-Length not allowed with Transfer-Encoding\n"; |
|
1013
|
8
|
|
|
|
|
|
goto got_bad_request; |
|
1014
|
|
|
|
|
|
|
} |
|
1015
|
74
|
|
|
|
|
|
c->receive_chunked = 1; |
|
1016
|
|
|
|
|
|
|
trace("got Transfer-Encoding: chunked on fd=%d\n", c->fd); |
|
1017
|
|
|
|
|
|
|
} |
|
1018
|
10
|
100
|
|
|
|
|
else if (is_identity) { |
|
1019
|
|
|
|
|
|
|
// identity means no encoding - treat as if no TE header |
|
1020
|
|
|
|
|
|
|
trace("got Transfer-Encoding: identity on fd=%d (ignored)\n", c->fd); |
|
1021
|
|
|
|
|
|
|
} |
|
1022
|
|
|
|
|
|
|
else { |
|
1023
|
|
|
|
|
|
|
// Unsupported transfer encoding |
|
1024
|
6
|
|
|
|
|
|
err_code = 501; |
|
1025
|
6
|
|
|
|
|
|
err = "Unsupported Transfer-Encoding\n"; |
|
1026
|
6
|
|
|
|
|
|
goto got_bad_request; |
|
1027
|
|
|
|
|
|
|
} |
|
1028
|
|
|
|
|
|
|
} |
|
1029
|
804
|
100
|
|
|
|
|
else if (unlikely(hdr->name_len == 4 && |
|
|
|
50
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
str_case_eq_fixed("host", hdr->name, 4))) |
|
1031
|
|
|
|
|
|
|
{ |
|
1032
|
546
|
|
|
|
|
|
got_host = 1; |
|
1033
|
|
|
|
|
|
|
} |
|
1034
|
|
|
|
|
|
|
} |
|
1035
|
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
// RFC 7230 Section 5.4: HTTP/1.1 requests MUST include Host header |
|
1037
|
504
|
100
|
|
|
|
|
if (unlikely(c->is_http11 && !got_host)) { |
|
|
|
100
|
|
|
|
|
|
|
1038
|
1
|
|
|
|
|
|
err_code = 400; |
|
1039
|
1
|
|
|
|
|
|
err = "Host header required for HTTP/1.1\n"; |
|
1040
|
1
|
|
|
|
|
|
goto got_bad_request; |
|
1041
|
|
|
|
|
|
|
} |
|
1042
|
|
|
|
|
|
|
|
|
1043
|
503
|
100
|
|
|
|
|
if (c->cached_max_conn_reqs > 0 && c->reqs >= c->cached_max_conn_reqs) { |
|
|
|
100
|
|
|
|
|
|
|
1044
|
1
|
|
|
|
|
|
c->is_keepalive = 0; |
|
1045
|
|
|
|
|
|
|
trace("reached max requests per connection (%d), will close after response\n", c->cached_max_conn_reqs); |
|
1046
|
|
|
|
|
|
|
} |
|
1047
|
|
|
|
|
|
|
|
|
1048
|
503
|
100
|
|
|
|
|
if (likely(next_req_follows) && !got_content_length && !c->receive_chunked) goto got_it_all; |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
1049
|
115
|
100
|
|
|
|
|
else if (likely(got_content_length)) goto got_cl; |
|
1050
|
70
|
100
|
|
|
|
|
else if (unlikely(c->receive_chunked)) goto got_chunked; |
|
1051
|
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
// body_is_required but no Content-Length or Transfer-Encoding |
|
1053
|
4
|
|
|
|
|
|
err_code = 411; |
|
1054
|
4
|
|
|
|
|
|
err = "Content-Length or Transfer-Encoding required\n"; |
|
1055
|
|
|
|
|
|
|
|
|
1056
|
72
|
|
|
|
|
|
got_bad_request: |
|
1057
|
72
|
|
|
|
|
|
respond_with_server_error(c, err, err_code); |
|
1058
|
72
|
|
|
|
|
|
return 0; |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
45
|
|
|
|
|
|
got_cl: |
|
1061
|
45
|
|
|
|
|
|
c->expected_cl = (ssize_t)expected; |
|
1062
|
45
|
|
|
|
|
|
c->received_cl = SvCUR(c->rbuf); |
|
1063
|
|
|
|
|
|
|
trace("expecting body %d size=%"Ssz_df" have=%"Ssz_df"\n", |
|
1064
|
|
|
|
|
|
|
c->fd, (Ssz)c->expected_cl, (Ssz)c->received_cl); |
|
1065
|
45
|
50
|
|
|
|
|
SvGROW(c->rbuf, c->expected_cl + 1); |
|
|
|
100
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
// don't have enough bytes to schedule immediately? |
|
1068
|
|
|
|
|
|
|
// unlikely = optimize for short requests |
|
1069
|
45
|
100
|
|
|
|
|
if (unlikely(c->expected_cl && c->received_cl < c->expected_cl)) { |
|
|
|
100
|
|
|
|
|
|
|
1070
|
12
|
|
|
|
|
|
send_100_continue(c); |
|
1071
|
12
|
|
|
|
|
|
return 1; |
|
1072
|
|
|
|
|
|
|
} |
|
1073
|
|
|
|
|
|
|
// fallthrough: have enough bytes |
|
1074
|
33
|
|
|
|
|
|
goto got_it_all; |
|
1075
|
|
|
|
|
|
|
|
|
1076
|
66
|
|
|
|
|
|
got_chunked: |
|
1077
|
|
|
|
|
|
|
// Initialize chunked transfer state |
|
1078
|
66
|
|
|
|
|
|
c->chunk_remaining = CHUNK_STATE_PARSE_SIZE; |
|
1079
|
66
|
|
|
|
|
|
c->chunk_count = 0; // reset chunk counter |
|
1080
|
66
|
|
|
|
|
|
c->trailer_count = 0; // reset trailer counter |
|
1081
|
66
|
|
|
|
|
|
c->expected_cl = 0; // will accumulate as we decode |
|
1082
|
66
|
|
|
|
|
|
c->received_cl = 0; |
|
1083
|
66
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_CHUNKED); |
|
1084
|
|
|
|
|
|
|
trace("starting chunked receive on fd=%d, have=%"Sz_uf" bytes\n", |
|
1085
|
|
|
|
|
|
|
c->fd, (Sz)SvCUR(c->rbuf)); |
|
1086
|
|
|
|
|
|
|
|
|
1087
|
66
|
|
|
|
|
|
send_100_continue(c); |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
// Try to parse any chunks we already have |
|
1090
|
|
|
|
|
|
|
{ |
|
1091
|
66
|
|
|
|
|
|
int ret = try_parse_chunked(c); |
|
1092
|
66
|
100
|
|
|
|
|
if (ret == 1) |
|
1093
|
5
|
|
|
|
|
|
return 1; // need more data |
|
1094
|
61
|
100
|
|
|
|
|
if (ret == -1) { |
|
1095
|
15
|
|
|
|
|
|
err_code = 400; |
|
1096
|
15
|
|
|
|
|
|
err = "Malformed chunked encoding\n"; |
|
1097
|
15
|
|
|
|
|
|
goto got_bad_request; |
|
1098
|
|
|
|
|
|
|
} |
|
1099
|
|
|
|
|
|
|
} |
|
1100
|
|
|
|
|
|
|
// fallthrough: chunked body complete |
|
1101
|
|
|
|
|
|
|
|
|
1102
|
46
|
|
|
|
|
|
got_it_all: |
|
1103
|
467
|
|
|
|
|
|
sched_request_callback(c); |
|
1104
|
467
|
|
|
|
|
|
return 0; |
|
1105
|
|
|
|
|
|
|
} |
|
1106
|
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
static void |
|
1108
|
1260
|
|
|
|
|
|
conn_write_ready (struct feer_conn *c) |
|
1109
|
|
|
|
|
|
|
{ |
|
1110
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
1111
|
|
|
|
|
|
|
if (c->is_h2_stream) { |
|
1112
|
|
|
|
|
|
|
h2_try_stream_write(aTHX_ c); |
|
1113
|
|
|
|
|
|
|
return; |
|
1114
|
|
|
|
|
|
|
} |
|
1115
|
|
|
|
|
|
|
#endif |
|
1116
|
|
|
|
|
|
|
|
|
1117
|
1260
|
100
|
|
|
|
|
if (c->in_callback) { |
|
1118
|
|
|
|
|
|
|
// Inside a callback: defer writes via the event loop write watcher. |
|
1119
|
|
|
|
|
|
|
// This ensures data gets flushed after the callback returns. |
|
1120
|
570
|
|
|
|
|
|
start_write_watcher(c); |
|
1121
|
570
|
|
|
|
|
|
return; |
|
1122
|
|
|
|
|
|
|
} |
|
1123
|
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
1125
|
690
|
100
|
|
|
|
|
if (c->tls) { |
|
1126
|
|
|
|
|
|
|
// Call TLS write path directly instead of deferring to write watcher. |
|
1127
|
|
|
|
|
|
|
// This mirrors how plain HTTP calls try_conn_write immediately below. |
|
1128
|
46
|
|
|
|
|
|
try_tls_conn_write(feersum_ev_loop, &c->write_ev_io, EV_WRITE); |
|
1129
|
46
|
|
|
|
|
|
return; |
|
1130
|
|
|
|
|
|
|
} |
|
1131
|
|
|
|
|
|
|
#endif |
|
1132
|
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
// attempt a non-blocking write immediately if we're not already |
|
1134
|
|
|
|
|
|
|
// waiting for writability |
|
1135
|
644
|
|
|
|
|
|
try_conn_write(feersum_ev_loop, &c->write_ev_io, EV_WRITE); |
|
1136
|
|
|
|
|
|
|
} |
|
1137
|
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
/* |
|
1139
|
|
|
|
|
|
|
* H2 stream dispatch helpers. |
|
1140
|
|
|
|
|
|
|
* These are static functions (not XS) so #ifdef works correctly. |
|
1141
|
|
|
|
|
|
|
* Called from XS CODE/PPCODE blocks that can't use #ifdef directly. |
|
1142
|
|
|
|
|
|
|
*/ |
|
1143
|
|
|
|
|
|
|
static int |
|
1144
|
73
|
|
|
|
|
|
h2_try_write_chunk (pTHX_ struct feer_conn *c, SV *body) |
|
1145
|
|
|
|
|
|
|
{ |
|
1146
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
1147
|
|
|
|
|
|
|
if (unlikely(c->is_h2_stream)) { |
|
1148
|
|
|
|
|
|
|
feersum_h2_write_chunk(aTHX_ c, body); |
|
1149
|
|
|
|
|
|
|
return 1; |
|
1150
|
|
|
|
|
|
|
} |
|
1151
|
|
|
|
|
|
|
#endif |
|
1152
|
|
|
|
|
|
|
PERL_UNUSED_VAR(c); PERL_UNUSED_VAR(body); |
|
1153
|
73
|
|
|
|
|
|
return 0; |
|
1154
|
|
|
|
|
|
|
} |
|
1155
|
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
static int |
|
1157
|
13
|
|
|
|
|
|
h2_is_stream (struct feer_conn *c) |
|
1158
|
|
|
|
|
|
|
{ |
|
1159
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
1160
|
|
|
|
|
|
|
if (unlikely(c->is_h2_stream)) |
|
1161
|
|
|
|
|
|
|
return 1; |
|
1162
|
|
|
|
|
|
|
#endif |
|
1163
|
|
|
|
|
|
|
PERL_UNUSED_VAR(c); |
|
1164
|
13
|
|
|
|
|
|
return 0; |
|
1165
|
|
|
|
|
|
|
} |
|
1166
|
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
INLINE_UNLESS_DEBUG static void |
|
1168
|
78
|
|
|
|
|
|
send_100_continue (struct feer_conn *c) |
|
1169
|
|
|
|
|
|
|
{ |
|
1170
|
78
|
100
|
|
|
|
|
if (likely(!c->expect_continue)) |
|
1171
|
65
|
|
|
|
|
|
return; |
|
1172
|
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
static const char continue_response[] = "HTTP/1.1 100 Continue" CRLF CRLF; |
|
1174
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_TLS |
|
1175
|
13
|
100
|
|
|
|
|
if (c->tls) { |
|
1176
|
2
|
|
|
|
|
|
feer_tls_send(c, continue_response, sizeof(continue_response) - 1); |
|
1177
|
2
|
|
|
|
|
|
feer_tls_flush_wbuf(c); |
|
1178
|
2
|
50
|
|
|
|
|
if (c->tls_wbuf.off > 0) |
|
1179
|
0
|
|
|
|
|
|
start_write_watcher(c); |
|
1180
|
2
|
|
|
|
|
|
c->expect_continue = 0; |
|
1181
|
2
|
|
|
|
|
|
return; |
|
1182
|
|
|
|
|
|
|
} |
|
1183
|
|
|
|
|
|
|
#endif |
|
1184
|
11
|
|
|
|
|
|
ssize_t wr = write(c->fd, continue_response, sizeof(continue_response) - 1); |
|
1185
|
|
|
|
|
|
|
// If write fails with EAGAIN or is partial, client will timeout and |
|
1186
|
|
|
|
|
|
|
// send body anyway (RFC 7231 recommends client wait ~1 second) |
|
1187
|
11
|
50
|
|
|
|
|
if (likely(wr > 0)) { |
|
1188
|
|
|
|
|
|
|
trace("sent 100 Continue to fd=%d\n", c->fd); |
|
1189
|
|
|
|
|
|
|
} |
|
1190
|
0
|
0
|
|
|
|
|
else if (wr < 0 && errno != EAGAIN && errno != EINTR) { |
|
|
|
0
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
trace("100 Continue write error fd=%d: %s\n", c->fd, strerror(errno)); |
|
1192
|
|
|
|
|
|
|
} |
|
1193
|
11
|
|
|
|
|
|
c->expect_continue = 0; // only send once |
|
1194
|
|
|
|
|
|
|
} |
|
1195
|
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
INLINE_UNLESS_DEBUG static void |
|
1197
|
709
|
|
|
|
|
|
free_feer_req (struct feer_req *req) |
|
1198
|
|
|
|
|
|
|
{ |
|
1199
|
709
|
100
|
|
|
|
|
if (unlikely(!req)) |
|
1200
|
133
|
|
|
|
|
|
return; |
|
1201
|
|
|
|
|
|
|
|
|
1202
|
576
|
100
|
|
|
|
|
if (req->buf) |
|
1203
|
418
|
|
|
|
|
|
SvREFCNT_dec(req->buf); |
|
1204
|
576
|
100
|
|
|
|
|
if (likely(req->path)) |
|
1205
|
283
|
|
|
|
|
|
SvREFCNT_dec(req->path); |
|
1206
|
576
|
100
|
|
|
|
|
if (likely(req->query)) |
|
1207
|
283
|
|
|
|
|
|
SvREFCNT_dec(req->query); |
|
1208
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
1209
|
|
|
|
|
|
|
if (req->h2_method_sv) |
|
1210
|
|
|
|
|
|
|
SvREFCNT_dec(req->h2_method_sv); |
|
1211
|
|
|
|
|
|
|
if (req->h2_uri_sv) |
|
1212
|
|
|
|
|
|
|
SvREFCNT_dec(req->h2_uri_sv); |
|
1213
|
|
|
|
|
|
|
#endif |
|
1214
|
576
|
50
|
|
|
|
|
FEER_REQ_FREE(req); |
|
1215
|
|
|
|
|
|
|
} |
|
1216
|
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
INLINE_UNLESS_DEBUG static void |
|
1218
|
709
|
|
|
|
|
|
free_request (struct feer_conn *c) |
|
1219
|
|
|
|
|
|
|
{ |
|
1220
|
709
|
|
|
|
|
|
free_feer_req(c->req); |
|
1221
|
709
|
|
|
|
|
|
c->req = NULL; |
|
1222
|
709
|
|
|
|
|
|
} |
|
1223
|
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
static void |
|
1225
|
190
|
|
|
|
|
|
respond_with_server_error (struct feer_conn *c, const char *msg, int err_code) |
|
1226
|
|
|
|
|
|
|
{ |
|
1227
|
|
|
|
|
|
|
SV *tmp; |
|
1228
|
|
|
|
|
|
|
STRLEN msg_len; |
|
1229
|
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
#ifdef FEERSUM_HAS_H2 |
|
1231
|
|
|
|
|
|
|
if (unlikely(c->is_h2_stream)) { |
|
1232
|
|
|
|
|
|
|
feersum_h2_respond_error(c, err_code); |
|
1233
|
|
|
|
|
|
|
return; |
|
1234
|
|
|
|
|
|
|
} |
|
1235
|
|
|
|
|
|
|
#endif |
|
1236
|
|
|
|
|
|
|
|
|
1237
|
190
|
100
|
|
|
|
|
if (unlikely(c->responding != RESPOND_NOT_STARTED)) { |
|
1238
|
1
|
|
|
|
|
|
trouble("Tried to send server error but already responding!"); |
|
1239
|
1
|
|
|
|
|
|
return; |
|
1240
|
|
|
|
|
|
|
} |
|
1241
|
|
|
|
|
|
|
|
|
1242
|
189
|
|
|
|
|
|
msg_len = strlen(msg); |
|
1243
|
|
|
|
|
|
|
assert(msg_len < INT_MAX); |
|
1244
|
|
|
|
|
|
|
|
|
1245
|
189
|
|
|
|
|
|
tmp = newSVpvf("HTTP/1.%d %d %s" CRLF |
|
1246
|
|
|
|
|
|
|
"Content-Type: text/plain" CRLF |
|
1247
|
|
|
|
|
|
|
"Connection: close" CRLF |
|
1248
|
|
|
|
|
|
|
"Cache-Control: no-cache, no-store" CRLF |
|
1249
|
|
|
|
|
|
|
"Content-Length: %"Ssz_df"" CRLFx2 |
|
1250
|
|
|
|
|
|
|
"%.*s", |
|
1251
|
|
|
|
|
|
|
c->is_http11 ? 1 : 0, |
|
1252
|
|
|
|
|
|
|
err_code, http_code_to_msg(err_code), |
|
1253
|
|
|
|
|
|
|
(Ssz)msg_len, |
|
1254
|
|
|
|
|
|
|
(int)msg_len, msg); |
|
1255
|
189
|
|
|
|
|
|
add_sv_to_wbuf(c, sv_2mortal(tmp)); |
|
1256
|
|
|
|
|
|
|
|
|
1257
|
189
|
|
|
|
|
|
stop_read_watcher(c); |
|
1258
|
189
|
|
|
|
|
|
stop_read_timer(c); |
|
1259
|
189
|
|
|
|
|
|
stop_header_timer(c); // Slowloris protection |
|
1260
|
189
|
|
|
|
|
|
change_responding_state(c, RESPOND_SHUTDOWN); |
|
1261
|
189
|
|
|
|
|
|
change_receiving_state(c, RECEIVE_SHUTDOWN); |
|
1262
|
189
|
|
|
|
|
|
c->is_keepalive = 0; |
|
1263
|
189
|
|
|
|
|
|
conn_write_ready(c); |
|
1264
|
|
|
|
|
|
|
} |
|
1265
|
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
|