File Coverage

feersum_h1.c.inc
Criterion Covered Total %
statement 515 620 83.0
branch 298 420 70.9
condition n/a
subroutine n/a
pod n/a
total 813 1040 78.1


line stmt bran cond sub pod time code
1             static int
2 640           try_parse_http(struct feer_conn *c, size_t last_read)
3             {
4 640           struct feer_req *req = c->req;
5 640 100         if (likely(!req)) {
6 624 100         FEER_REQ_ALLOC(req);
7 624           c->req = req;
8             }
9              
10             // phr_parse_request resets num_headers on each call; must restore to max
11 640           req->num_headers = MAX_HEADERS;
12              
13 1280           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 640           req->headers, &req->num_headers,
17 640           (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 717           finish_receiving(struct feer_conn *c) {
23 717           change_receiving_state(c, RECEIVE_SHUTDOWN);
24 717           stop_read_watcher(c);
25 717           stop_read_timer(c);
26 717           stop_header_timer(c);
27 717           }
28              
29             static void
30 731           try_conn_read(EV_P_ ev_io *w, int revents)
31             {
32 731           dCONN;
33 731           SvREFCNT_inc_void_NN(c->self);
34 731           feer_conn_set_busy(c);
35 731           ssize_t got_n = 0;
36              
37 731 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 626 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 626 50         if (unlikely(c->receiving == RECEIVE_SHUTDOWN))
47 0           goto dont_read_again;
48              
49             trace("try read %d\n",w->fd);
50              
51 626 100         if (unlikely(!c->rbuf)) { // unlikely = optimize for keepalive requests
52             trace("init rbuf for %d\n",w->fd);
53 574           c->rbuf = newSV(READ_BUFSZ + 1);
54 574           SvPOK_on(c->rbuf);
55             }
56              
57 626           ssize_t space_free = SvLEN(c->rbuf) - SvCUR(c->rbuf);
58 626 100         if (unlikely(space_free < READ_BUFSZ)) { // unlikely = optimize for small
59 29           size_t cur_len = SvLEN(c->rbuf);
60             // DoS protection: limit buffer growth (especially for chunked encoding)
61 29 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 29           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 29 50         SvGROW(c->rbuf, new_len);
    50          
71 29           space_free += READ_GROW_FACTOR*READ_BUFSZ;
72             }
73              
74 626           char *cur = SvPVX(c->rbuf) + SvCUR(c->rbuf);
75 626           got_n = read(w->fd, cur, space_free);
76              
77 626 100         if (unlikely(got_n <= 0)) {
78 31 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 29           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 595           SvCUR(c->rbuf) += got_n;
90 595 100         if (c->receiving == RECEIVE_WAIT)
91 12           change_receiving_state(c, RECEIVE_HEADERS);
92 595           goto try_parse;
93              
94 105           pipelined:
95 105           got_n = c->pipelined;
96 105           c->pipelined = 0;
97              
98 700           try_parse:
99             // Handle PROXY protocol header state
100 731 100         if (unlikely(c->receiving == RECEIVE_PROXY_HEADER)) {
101 136           int ret = try_parse_proxy_header(c);
102 136 100         if (ret == -1) {
103 103           respond_with_server_error(c, "Invalid PROXY protocol header\n", 400);
104 103           goto dont_read_again;
105             }
106 33 100         if (ret == -2) goto try_read_again_reset_timer;
107              
108             // Consume parsed bytes from buffer
109 31           STRLEN remaining = SvCUR(c->rbuf) - ret;
110 31 50         if (remaining > 0)
111 31           memmove(SvPVX(c->rbuf), SvPVX(c->rbuf) + ret, remaining);
112 31           SvCUR_set(c->rbuf, remaining);
113              
114             // Clear cached remote addr/port (force regeneration with new address)
115 31 50         feer_clear_remote_cache(c);
    50          
116              
117             // Transition to HTTP parsing
118 31           change_receiving_state(c, RECEIVE_HEADERS);
119 31 50         if (remaining > 0) {
120 31           got_n = remaining;
121 31           goto try_parse;
122             }
123 0           goto try_read_again_reset_timer;
124             }
125              
126             // likely = optimize for small requests
127 595 100         if (likely(c->receiving <= RECEIVE_HEADERS)) {
128 581           int ret = try_parse_http(c, (size_t)got_n);
129 581 100         if (ret == -1) goto try_read_bad;
130             #ifdef TCP_DEFER_ACCEPT
131 570 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 548 100         if (process_request_headers(c, ret))
140 17           goto try_read_again_reset_timer;
141             else
142 531           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 31           try_read_error:
186             trace("READ ERROR %d, refcnt=%d\n", w->fd, SvREFCNT(c->self));
187 31           change_receiving_state(c, RECEIVE_SHUTDOWN);
188 31           change_responding_state(c, RESPOND_SHUTDOWN);
189 31           stop_all_watchers(c);
190 31           goto try_read_cleanup;
191              
192 11           try_read_bad:
193             trace("bad request %d\n", w->fd);
194 11           respond_with_server_error(c, "Malformed request\n", 400);
195             // fallthrough (respond_with_server_error sets is_keepalive=0):
196 658           dont_read_again:
197             trace("done reading %d\n", w->fd);
198 658           finish_receiving(c);
199 658           goto try_read_cleanup;
200              
201 42           try_read_again_reset_timer:
202             trace("(reset read timer) %d\n", w->fd);
203 42           restart_read_timer(c);
204             // fallthrough:
205 42           try_read_again:
206             trace("read again %d\n", w->fd);
207 42           start_read_watcher(c);
208              
209 731           try_read_cleanup:
210 731           SvREFCNT_dec(c->self);
211 731           }
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             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 11 50         if (likely(c->responding == RESPOND_NOT_STARTED) && c->receiving >= RECEIVE_HEADERS) {
    100          
249             #ifdef FEERSUM_HAS_TLS
250 5 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 5 50         if (c->receiving == RECEIVE_PROXY_HEADER) {
261 0           msg = "PROXY protocol header timeout.";
262             }
263 5 100         else if (c->receiving == RECEIVE_HEADERS) {
264 2           msg = "Headers took too long.";
265             }
266             else {
267 3           msg = "Timeout reading body.";
268             }
269 5           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 6           read_timeout_cleanup:
279 6           SvREFCNT_dec(c->self);
280 6           }
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 636           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 636 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 636           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 636 100         if (srvr->header_timeout > 0.0) {
360 635           ev_timer_set(&c->header_ev_timer, srvr->header_timeout, 0.0); // one-shot
361 635           ev_timer_start(feersum_ev_loop, &c->header_ev_timer);
362 635           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 636 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 68           start_read_watcher(c);
373             } else
374             #endif
375             {
376 568           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 636           SvREFCNT_dec(c->self);
395 636           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 1197           try_accept_one(EV_P_ struct feer_listen *lsnr, struct feer_server *srvr)
414             {
415             struct sockaddr_storage sa_buf;
416 1197           socklen_t sa_len = sizeof(struct sockaddr_storage);
417 1197           errno = 0;
418             #ifdef HAS_ACCEPT4
419 1197           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 1197 100         if (fd == -1) {
425 560 50         if (errno == EINTR) return 1;
426 560 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 560           return 0;
435             }
436              
437 637 100         if (srvr->max_connections > 0 && srvr->active_conns >= srvr->max_connections) {
    100          
438 3 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 636           setup_accepted_conn(EV_A, fd, (struct sockaddr *)&sa_buf, sa_len, srvr, lsnr);
447 636           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 561           accept_cb (EV_P_ ev_io *w, int revents)
479             {
480 561           struct feer_listen *lsnr = (struct feer_listen *)w->data;
481 561           struct feer_server *srvr = lsnr->server;
482              
483 561 50         if (unlikely(srvr->shutting_down)) {
484 0           ev_io_stop(EV_A, w);
485 0           return;
486             }
487              
488 561 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 561           int accept_count = 0;
497 1197 50         while (accept_count++ < srvr->max_accept_per_loop) {
498 1197 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 215           setup_accept_watcher(struct feer_listen *lsnr, int listen_fd)
505             {
506 215           struct feer_server *srvr = lsnr->server;
507             #if defined(__linux__) && defined(EPOLLEXCLUSIVE)
508 215 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 213           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 215           lsnr->accept_w.data = (void *)lsnr;
543 215           ev_set_priority(&lsnr->accept_w, srvr->accept_priority);
544 215           ev_init(&lsnr->emfile_w, emfile_retry_cb);
545 215           lsnr->emfile_w.data = (void *)lsnr;
546 215           }
547              
548             static void
549 532           sched_request_callback (struct feer_conn *c)
550             {
551 532           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 532           rinq_push(&server->request_ready_rinq, c);
557 532           SvREFCNT_inc_void_NN(c->self); // for the rinq
558 532 100         if (!ev_is_active(&server->ei)) {
559             trace("starting idle watcher\n");
560 368           ev_idle_start(feersum_ev_loop, &server->ei);
561             }
562 532           }
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 71           try_parse_chunked (struct feer_conn *c)
570             {
571 71 50         if (!c->rbuf) return 1; // need data
572              
573 71           char *buf = SvPVX(c->rbuf);
574 71           STRLEN buf_len = SvCUR(c->rbuf);
575             // received_cl tracks decoded position; should always be non-negative
576 71           STRLEN read_pos = (c->received_cl >= 0) ? (STRLEN)c->received_cl : 0;
577 71           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 5525 100         while (read_pos < buf_len) {
583 5523 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 5521 100         else if (c->chunk_remaining == CHUNK_STATE_PARSE_SIZE) {
597             // Parsing chunk size line: find CRLF
598 2766           char *line_start = buf + read_pos;
599 2766           char *line_end = NULL;
600 2766           STRLEN remaining = buf_len - read_pos;
601             STRLEN i;
602              
603             // Look for CRLF
604 5856 50         for (i = 0; i + 1 < remaining; i++) {
605 5856 100         if (line_start[i] == '\r' && line_start[i+1] == '\n') {
    50          
606 2766           line_end = line_start + i;
607 2766           break;
608             }
609             }
610              
611 2766 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 2766           UV chunk_size = 0;
620 2766           int hex_digits = 0;
621 2766           char *p = line_start;
622 5548 100         while (p < line_end) {
623 2798           unsigned char ch = (unsigned char)*p;
624 2798           unsigned char val = hex_decode_table[ch];
625 2798 100         if (val != 0xFF) {
626 2783           hex_digits++;
627             // Check for potential overflow BEFORE shifting
628 2783 100         if (chunk_size > (UV_MAX >> 4)) {
629             trace("chunked: chunk size overflow\n");
630 1           return -1;
631             }
632 2782           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 2782           p++;
642             }
643              
644             // Reject chunk size lines with no hex digits (e.g., ";ext\r\n")
645 2759 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 2755           read_pos = (line_end - buf) + 2;
655              
656 2755 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 2703 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 2703 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 2703           c->chunk_count++;
681 2703 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 2703 50         if (chunk_size > (UV)SSIZE_MAX) return -1;
687 2703           c->chunk_remaining = (ssize_t)chunk_size;
688             }
689 2755 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 2703           STRLEN remaining = buf_len - read_pos;
727 2703           STRLEN to_copy = (STRLEN)c->chunk_remaining;
728 2703 100         if (to_copy > remaining)
729 2           to_copy = remaining;
730              
731             // Move chunk data to write position (decode in place)
732 2703 50         if (write_pos != read_pos && to_copy > 0) {
    50          
733 2703           memmove(buf + write_pos, buf + read_pos, to_copy);
734             }
735 2703           write_pos += to_copy;
736 2703           read_pos += to_copy;
737 2703           c->chunk_remaining -= to_copy;
738 2703           c->received_cl = write_pos;
739              
740 2703 100         if (c->chunk_remaining > 0) {
741             // Need more data for this chunk
742 2           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 6 100         if (read_pos > write_pos) {
764 4 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 4           SvCUR_set(c->rbuf, write_pos);
770             }
771             }
772             // else: read_pos == write_pos, buffer is already compact, SvCUR unchanged
773 6           c->received_cl = write_pos;
774 6           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 607           process_request_headers (struct feer_conn *c, int body_offset)
781             {
782             int err_code;
783             const char *err;
784 607           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 607           stop_header_timer(c);
797              
798             trace("processing headers %d minor_version=%d\n",c->fd,req->minor_version);
799 607           bool body_is_required = 0;
800 607           bool next_req_follows = 0;
801 607           bool got_content_length = 0;
802              
803 607           c->is_http11 = (req->minor_version == 1);
804 607 100         c->is_keepalive = c->cached_keepalive_default && c->is_http11;
    100          
805 607           c->expect_continue = 0; // reset for each request
806 607           c->receive_chunked = 0; // reset for each request
807 607           c->reqs++;
808              
809 607           change_receiving_state(c, RECEIVE_BODY);
810 607           c->expected_cl = 0;
811 607           c->received_cl = 0;
812              
813             // Dispatch by method length first to minimize string comparisons
814 607           switch (req->method_len) {
815 434           case 3:
816 434 100         if (likely(memcmp(req->method, "GET", 3) == 0)) {
817 433           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 434           break;
824 166           case 4:
825 166 100         if (likely(memcmp(req->method, "POST", 4) == 0)) {
826 165           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 166           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 3           unsupported_method:
856 3           err = "Feersum doesn't support that method yet\n";
857 3           err_code = 405;
858 3           goto got_bad_request;
859             }
860              
861             // RFC 7230: URI length check (414 URI Too Long)
862 604 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 601           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 601 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 601           from += body_offset;
888 601           STRLEN need = from_len - body_offset;
889             trace("new rbuf for body %d need=%"Sz_uf"\n", c->fd, (Sz)need);
890 601 100         SV *new_rbuf = newSVpvn(need > 0 ? from : "", need);
891              
892 601           req->buf = c->rbuf;
893 601           c->rbuf = new_rbuf;
894 601           SvCUR_set(req->buf, body_offset);
895              
896             // determine how much we need to read
897             size_t i;
898 601           UV expected = 0;
899 601           bool got_host = 0;
900 601           bool got_transfer_encoding = 0;
901 2041 100         for (i=0; i < req->num_headers; i++) {
902 1485           struct phr_header *hdr = &req->headers[i];
903             // RFC 7230: reject obsolete header line folding (obs-fold)
904 1485 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 1484 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 1482 100         if (unlikely(hdr->name_len == 14 &&
    50          
917             str_case_eq_fixed("content-length", hdr->name, 14)))
918 77           {
919             // RFC 7230 3.3.3: reject if Transfer-Encoding was already seen
920 88 100         if (c->receive_chunked) {
921 3           err_code = 400;
922 3           err = "Content-Length not allowed with Transfer-Encoding\n";
923 11           goto got_bad_request;
924             }
925 85           UV new_expected = 0;
926 85           int g = grok_number(hdr->value, hdr->value_len, &new_expected);
927 85 100         if (likely(g == IS_NUMBER_IN_UV)) {
928 83 100         if (unlikely(new_expected > (UV)c->cached_max_body_len)) {
929 4           err_code = 413;
930 4           err = "Content length exceeds maximum\n";
931 4           goto got_bad_request;
932             }
933             // RFC 7230: reject multiple Content-Length with different values
934 79 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 77           expected = new_expected;
940 77           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 1394 100         else if (unlikely(hdr->name_len == 10 &&
    100          
949             str_case_eq_fixed("connection", hdr->name, 10)))
950             {
951 395 100         if (c->is_http11 && c->is_keepalive &&
    100          
952 92 100         hdr->value_len == 5 && str_case_eq_fixed("close", hdr->value, 5))
    50          
953             {
954 83           c->is_keepalive = 0;
955             trace("setting conn %d to close after response\n", c->fd);
956             }
957 312 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 395 100         if (next_req_follows && c->receive_chunked && (!c->is_http11 || got_host)) break;
    50          
    0          
    0          
964             }
965 999 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 977 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 79           {
985             // RFC 7230 3.3.3: reject multiple Transfer-Encoding headers
986             // to prevent request smuggling attacks
987 99 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 93           got_transfer_encoding = 1;
993              
994             // RFC 7230: Accept "chunked" with optional extensions
995             // Valid formats: "chunked", "chunked;ext=val", "chunked ; ext"
996 91 100         bool is_chunked = (hdr->value_len >= 7 &&
997 184 100         str_case_eq_fixed("chunked", hdr->value, 7) &&
998 83 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 98           bool is_identity = (hdr->value_len == 8 &&
1005 5           str_case_eq_fixed("identity", hdr->value, 8));
1006              
1007 93 100         if (is_chunked) {
1008             // RFC 7230 3.3.3: reject if Content-Length is also present
1009             // This prevents request smuggling attacks
1010 83 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 75           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 878 100         else if (unlikely(hdr->name_len == 4 &&
    50          
1030             str_case_eq_fixed("host", hdr->name, 4)))
1031             {
1032 602 100         if (unlikely(got_host)) {
1033 2           err_code = 400;
1034 2           err = "Duplicate Host header\n";
1035 2           goto got_bad_request;
1036             }
1037 600           got_host = 1;
1038             }
1039             }
1040              
1041             // RFC 7230 Section 5.4: HTTP/1.1 requests MUST include Host header
1042 556 100         if (unlikely(c->is_http11 && !got_host)) {
    100          
1043 1           err_code = 400;
1044 1           err = "Host header required for HTTP/1.1\n";
1045 1           goto got_bad_request;
1046             }
1047              
1048 555 100         if (c->cached_max_conn_reqs > 0 && c->reqs >= c->cached_max_conn_reqs) {
    100          
1049 1           c->is_keepalive = 0;
1050             trace("reached max requests per connection (%d), will close after response\n", c->cached_max_conn_reqs);
1051             }
1052              
1053 555 100         if (likely(next_req_follows) && !got_content_length && !c->receive_chunked) goto got_it_all;
    50          
    50          
1054 128 100         else if (likely(got_content_length)) goto got_cl;
1055 71 100         else if (unlikely(c->receive_chunked)) goto got_chunked;
1056              
1057             // body_is_required but no Content-Length or Transfer-Encoding
1058 4           err_code = 411;
1059 4           err = "Content-Length or Transfer-Encoding required\n";
1060              
1061 71           got_bad_request:
1062 71           respond_with_server_error(c, err, err_code);
1063 71           return 0;
1064              
1065 57           got_cl:
1066 57           c->expected_cl = (ssize_t)expected;
1067 57           c->received_cl = SvCUR(c->rbuf);
1068             trace("expecting body %d size=%"Ssz_df" have=%"Ssz_df"\n",
1069             c->fd, (Ssz)c->expected_cl, (Ssz)c->received_cl);
1070 57 50         SvGROW(c->rbuf, c->expected_cl + 1);
    100          
1071              
1072             // don't have enough bytes to schedule immediately?
1073             // unlikely = optimize for short requests
1074 57 100         if (unlikely(c->expected_cl && c->received_cl < c->expected_cl)) {
    100          
1075 13           send_100_continue(c);
1076 13           return 1;
1077             }
1078             // fallthrough: have enough bytes
1079 44           goto got_it_all;
1080              
1081 67           got_chunked:
1082             // Initialize chunked transfer state
1083 67           c->chunk_remaining = CHUNK_STATE_PARSE_SIZE;
1084 67           c->chunk_count = 0; // reset chunk counter
1085 67           c->trailer_count = 0; // reset trailer counter
1086 67           c->expected_cl = 0; // will accumulate as we decode
1087 67           c->received_cl = 0;
1088 67           change_receiving_state(c, RECEIVE_CHUNKED);
1089             trace("starting chunked receive on fd=%d, have=%"Sz_uf" bytes\n",
1090             c->fd, (Sz)SvCUR(c->rbuf));
1091              
1092 67           send_100_continue(c);
1093              
1094             // Try to parse any chunks we already have
1095             {
1096 67           int ret = try_parse_chunked(c);
1097 67 100         if (ret == 1)
1098 6           return 1; // need more data
1099 61 100         if (ret == -1) {
1100 15           err_code = 400;
1101 15           err = "Malformed chunked encoding\n";
1102 15           goto got_bad_request;
1103             }
1104             }
1105             // fallthrough: chunked body complete
1106              
1107 46           got_it_all:
1108 517           sched_request_callback(c);
1109 517           return 0;
1110             }
1111              
1112             static void
1113 1358           conn_write_ready (struct feer_conn *c)
1114             {
1115             #ifdef FEERSUM_HAS_H2
1116             if (c->is_h2_stream) {
1117             h2_try_stream_write(aTHX_ c);
1118             return;
1119             }
1120             #endif
1121              
1122 1358 100         if (c->in_callback) {
1123             // Inside a callback: defer writes via the event loop write watcher.
1124             // This ensures data gets flushed after the callback returns.
1125 617           start_write_watcher(c);
1126 617           return;
1127             }
1128              
1129             #ifdef FEERSUM_HAS_TLS
1130 741 100         if (c->tls) {
1131             // Call TLS write path directly instead of deferring to write watcher.
1132             // This mirrors how plain HTTP calls try_conn_write immediately below.
1133 50           try_tls_conn_write(feersum_ev_loop, &c->write_ev_io, EV_WRITE);
1134 50           return;
1135             }
1136             #endif
1137              
1138             // attempt a non-blocking write immediately if we're not already
1139             // waiting for writability
1140 691           try_conn_write(feersum_ev_loop, &c->write_ev_io, EV_WRITE);
1141             }
1142              
1143             /*
1144             * H2 stream dispatch helpers.
1145             * These are static functions (not XS) so #ifdef works correctly.
1146             * Called from XS CODE/PPCODE blocks that can't use #ifdef directly.
1147             */
1148             static int
1149 73           h2_try_write_chunk (pTHX_ struct feer_conn *c, SV *body)
1150             {
1151             #ifdef FEERSUM_HAS_H2
1152             if (unlikely(c->is_h2_stream)) {
1153             feersum_h2_write_chunk(aTHX_ c, body);
1154             return 1;
1155             }
1156             #endif
1157             PERL_UNUSED_VAR(c); PERL_UNUSED_VAR(body);
1158 73           return 0;
1159             }
1160              
1161             static int
1162 14           h2_is_stream (struct feer_conn *c)
1163             {
1164             #ifdef FEERSUM_HAS_H2
1165             if (unlikely(c->is_h2_stream))
1166             return 1;
1167             #endif
1168             PERL_UNUSED_VAR(c);
1169 14           return 0;
1170             }
1171              
1172             INLINE_UNLESS_DEBUG static void
1173 80           send_100_continue (struct feer_conn *c)
1174             {
1175 80 100         if (likely(!c->expect_continue))
1176 67           return;
1177              
1178             static const char continue_response[] = "HTTP/1.1 100 Continue" CRLF CRLF;
1179             #ifdef FEERSUM_HAS_TLS
1180 13 100         if (c->tls) {
1181 2           feer_tls_send(c, continue_response, sizeof(continue_response) - 1);
1182 2           feer_tls_flush_wbuf(c);
1183 2 50         if (c->tls_wbuf.off > 0)
1184 0           start_write_watcher(c);
1185 2           c->expect_continue = 0;
1186 2           return;
1187             }
1188             #endif
1189 11           ssize_t wr = write(c->fd, continue_response, sizeof(continue_response) - 1);
1190             // If write fails with EAGAIN or is partial, client will timeout and
1191             // send body anyway (RFC 7231 recommends client wait ~1 second)
1192 11 50         if (likely(wr > 0)) {
1193             trace("sent 100 Continue to fd=%d\n", c->fd);
1194             }
1195 0 0         else if (wr < 0 && errno != EAGAIN && errno != EINTR) {
    0          
1196             trace("100 Continue write error fd=%d: %s\n", c->fd, strerror(errno));
1197             }
1198 11           c->expect_continue = 0; // only send once
1199             }
1200              
1201             INLINE_UNLESS_DEBUG static void
1202 770           free_feer_req (struct feer_req *req)
1203             {
1204 770 100         if (unlikely(!req))
1205 148           return;
1206              
1207 622 100         if (req->buf)
1208 457           SvREFCNT_dec(req->buf);
1209 622 100         if (likely(req->path))
1210 317           SvREFCNT_dec(req->path);
1211 622 100         if (likely(req->query))
1212 317           SvREFCNT_dec(req->query);
1213             #ifdef FEERSUM_HAS_H2
1214             if (req->h2_method_sv)
1215             SvREFCNT_dec(req->h2_method_sv);
1216             if (req->h2_uri_sv)
1217             SvREFCNT_dec(req->h2_uri_sv);
1218             #endif
1219 622 50         FEER_REQ_FREE(req);
1220             }
1221              
1222             INLINE_UNLESS_DEBUG static void
1223 770           free_request (struct feer_conn *c)
1224             {
1225 770           free_feer_req(c->req);
1226 770           c->req = NULL;
1227 770           }
1228              
1229             static void
1230 197           respond_with_server_error (struct feer_conn *c, const char *msg, int err_code)
1231             {
1232             SV *tmp;
1233             STRLEN msg_len;
1234              
1235             #ifdef FEERSUM_HAS_H2
1236             if (unlikely(c->is_h2_stream)) {
1237             feersum_h2_respond_error(c, err_code);
1238             return;
1239             }
1240             #endif
1241              
1242 197 100         if (unlikely(c->responding != RESPOND_NOT_STARTED)) {
1243 1           trouble("Tried to send server error but already responding!");
1244 1           return;
1245             }
1246              
1247 196           msg_len = strlen(msg);
1248             assert(msg_len < INT_MAX);
1249              
1250 196           tmp = newSVpvf("HTTP/1.%d %d %s" CRLF
1251             "Content-Type: text/plain" CRLF
1252             "Connection: close" CRLF
1253             "Cache-Control: no-cache, no-store" CRLF
1254             "Content-Length: %"Ssz_df"" CRLFx2
1255             "%.*s",
1256             c->is_http11 ? 1 : 0,
1257             err_code, http_code_to_msg(err_code),
1258             (Ssz)msg_len,
1259             (int)msg_len, msg);
1260 196           add_sv_to_wbuf(c, sv_2mortal(tmp));
1261              
1262 196           stop_read_watcher(c);
1263 196           stop_read_timer(c);
1264 196           stop_header_timer(c); // Slowloris protection
1265 196           change_responding_state(c, RESPOND_SHUTDOWN);
1266 196           change_receiving_state(c, RECEIVE_SHUTDOWN);
1267 196           c->is_keepalive = 0;
1268 196           conn_write_ready(c);
1269             }
1270              
1271