File Coverage

feersum_h1.c.inc
Criterion Covered Total %
statement 514 616 83.4
branch 299 422 70.8
condition n/a
subroutine n/a
pod n/a
total 813 1038 78.3


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