File Coverage

feersum_h1.c.inc
Criterion Covered Total %
statement 511 616 82.9
branch 295 418 70.5
condition n/a
subroutine n/a
pod n/a
total 806 1034 77.9


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