File Coverage

feersum_psgi.c.inc
Criterion Covered Total %
statement 612 736 83.1
branch 440 718 61.2
condition n/a
subroutine n/a
pod n/a
total 1052 1454 72.3


line stmt bran cond sub pod time code
1              
2             // Extract first IP from X-Forwarded-For header (leftmost = original client)
3             static SV*
4 18           extract_forwarded_addr(pTHX_ struct feer_req *r)
5             {
6             size_t val_len;
7 18           const char *val = find_header_value(r, "x-forwarded-for", 15, &val_len);
8 18 100         if (!val || val_len == 0) return NULL;
    50          
9              
10             // Skip leading whitespace
11 15 50         while (val_len > 0 && (*val == ' ' || *val == '\t')) { val++; val_len--; }
    50          
    50          
12 15 50         if (val_len == 0) return NULL;
13              
14             // Find end of first IP (comma or space or end)
15 15           size_t ip_len = 0;
16 187 100         while (ip_len < val_len && val[ip_len] != ',' && val[ip_len] != ' ') ip_len++;
    100          
    100          
17              
18 15 50         if (ip_len == 0 || ip_len > 45) return NULL; // max IPv6 length is 45 chars
    50          
19              
20             // Copy to null-terminated buffer for inet_pton validation
21             char ip_buf[46];
22 15           memcpy(ip_buf, val, ip_len);
23 15           ip_buf[ip_len] = '\0';
24              
25             // Validate as IPv4 or IPv6 address using inet_pton
26             struct in_addr addr4;
27             struct in6_addr addr6;
28 15 100         if (inet_pton(AF_INET, ip_buf, &addr4) == 1) {
29 9           return newSVpvn(val, ip_len); // valid IPv4
30             }
31 6 100         if (inet_pton(AF_INET6, ip_buf, &addr6) == 1) {
32 2           return newSVpvn(val, ip_len); // valid IPv6
33             }
34              
35             // Not a valid IP address - return NULL (caller will use original REMOTE_ADDR)
36             trace("X-Forwarded-For contains invalid IP: %s\n", ip_buf);
37 4           return NULL;
38             }
39              
40             static SV*
41 17           extract_forwarded_proto(pTHX_ struct feer_req *r)
42             {
43             size_t val_len;
44 17           const char *val = find_header_value(r, "x-forwarded-proto", 17, &val_len);
45 17 100         if (!val || val_len == 0) return NULL;
    50          
46              
47             // Skip whitespace
48 7 50         while (val_len > 0 && (*val == ' ' || *val == '\t')) { val++; val_len--; }
    50          
    50          
49              
50             // Check for exact https/http (reject "httpx", "https2", etc.)
51 7 50         if (val_len >= 5 && str_case_eq_fixed("https", val, 5) &&
    50          
52 7 100         (val_len == 5 || val[5] == ' ' || val[5] == '\t' || val[5] == ','))
    50          
    50          
    50          
53 7           return newSVpvs("https");
54 0 0         if (val_len >= 4 && str_case_eq_fixed("http", val, 4) &&
    0          
55 0 0         (val_len == 4 || val[4] == ' ' || val[4] == '\t' || val[4] == ','))
    0          
    0          
    0          
56 0           return newSVpvs("http");
57              
58 0           return NULL;
59             }
60              
61             /* Determine the URL scheme for a connection.
62             * Returns a new SV ("https" or forwarded proto), or NULL for default "http". */
63             static SV *
64 308           feer_determine_url_scheme(pTHX_ struct feer_conn *c)
65             {
66             #ifdef FEERSUM_HAS_H2
67             /* H2 requires TLS (ALPN), so scheme is always https.
68             * The :scheme pseudo-header is validated but not propagated. */
69             if (c->is_h2_stream) return newSVpvs("https");
70             #endif
71             #ifdef FEERSUM_HAS_TLS
72 308 100         if (c->tls) return newSVpvs("https");
73             #endif
74 269 100         if (c->proxy_ssl) return newSVpvs("https");
75 267 100         if (c->proxy_proto_version > 0 && c->proxy_dst_port == 443)
    100          
76 5           return newSVpvs("https");
77 262 100         if (c->cached_use_reverse_proxy && c->req) {
    50          
78 17           SV *fwd = extract_forwarded_proto(aTHX_ c->req);
79 17 100         if (fwd) return fwd;
80             }
81 255           return NULL;
82             }
83              
84             static void
85 57           feersum_init_psgi_env_constants(pTHX)
86             {
87 57 50         if (psgi_env_version) return;
88 57           psgi_env_version = newRV((SV*)psgi_ver);
89 57           psgi_env_errors = newRV((SV*)PL_stderrgv);
90             }
91              
92             static HV*
93 297           feersum_build_psgi_env(pTHX)
94             {
95 297           HV *e = newHV();
96             /* ~13 constants + ~10 per-request + ~15 headers ~= 38 */
97 297           hv_ksplit(e, 48);
98              
99 297           hv_stores(e, "psgi.version", SvREFCNT_inc_simple_NN(psgi_env_version));
100 297           hv_stores(e, "psgi.errors", SvREFCNT_inc_simple_NN(psgi_env_errors));
101              
102             /* PL_sv_yes/no are immortal — refcount inc is safe */
103 297           hv_stores(e, "psgi.run_once", SvREFCNT_inc_simple_NN(&PL_sv_no));
104 297           hv_stores(e, "psgi.nonblocking", SvREFCNT_inc_simple_NN(&PL_sv_yes));
105 297           hv_stores(e, "psgi.multithread", SvREFCNT_inc_simple_NN(&PL_sv_no));
106 297           hv_stores(e, "psgi.streaming", SvREFCNT_inc_simple_NN(&PL_sv_yes));
107 297           hv_stores(e, "psgix.input.buffered", SvREFCNT_inc_simple_NN(&PL_sv_yes));
108 297           hv_stores(e, "psgix.output.buffered", SvREFCNT_inc_simple_NN(&PL_sv_yes));
109 297           hv_stores(e, "psgix.body.scalar_refs", SvREFCNT_inc_simple_NN(&PL_sv_yes));
110 297           hv_stores(e, "psgix.output.guard", SvREFCNT_inc_simple_NN(&PL_sv_yes));
111              
112 297           hv_stores(e, "SCRIPT_NAME", newSVpvs(""));
113              
114 297           return e;
115             }
116              
117             static HV*
118 297           feersum_env(pTHX_ struct feer_conn *c)
119             {
120             HV *e;
121             int i,j;
122 297           struct feer_req *r = c->req;
123              
124 297 100         if (unlikely(!psgi_env_version))
125 57           feersum_init_psgi_env_constants(aTHX);
126              
127 297           e = feersum_build_psgi_env(aTHX);
128              
129             trace("generating header (fd %d) %.*s\n",
130             c->fd, (int)r->uri_len, r->uri);
131              
132             // SERVER_NAME and SERVER_PORT - copy because these SVs are SvREADONLY
133             // and middleware may modify in-place (e.g., Plack::Middleware::ReverseProxy).
134             {
135 297           struct feer_listen *conn_lsnr = c->listener;
136 297 50         hv_stores(e, "SERVER_NAME",
137             conn_lsnr->server_name ? newSVsv(conn_lsnr->server_name) : newSVpvs(""));
138 297 50         hv_stores(e, "SERVER_PORT",
139             conn_lsnr->server_port ? newSVsv(conn_lsnr->server_port) : newSVpvs("0"));
140             }
141 297           hv_stores(e, "REQUEST_URI", feersum_env_uri(aTHX_ r));
142             #ifdef FEERSUM_HAS_H2
143             hv_stores(e, "REQUEST_METHOD", feersum_env_method_h2(aTHX_ c, r));
144             if (unlikely(c->is_h2_stream))
145             hv_stores(e, "SERVER_PROTOCOL", newSVpvs("HTTP/2"));
146             else
147             hv_stores(e, "SERVER_PROTOCOL", SvREFCNT_inc_simple_NN(feersum_env_protocol(aTHX_ r)));
148             #else
149 297           hv_stores(e, "REQUEST_METHOD", feersum_env_method(aTHX_ r));
150 297           hv_stores(e, "SERVER_PROTOCOL", SvREFCNT_inc_simple_NN(feersum_env_protocol(aTHX_ r)));
151             #endif
152              
153 297           feersum_set_conn_remote_info(aTHX_ c);
154              
155             // Reverse proxy mode: trust X-Forwarded-For for REMOTE_ADDR
156 297 100         if (c->cached_use_reverse_proxy) {
157 13           SV *fwd_addr = extract_forwarded_addr(aTHX_ r);
158 13 100         hv_stores(e, "REMOTE_ADDR", fwd_addr ? fwd_addr : SvREFCNT_inc_simple_NN(c->remote_addr));
159             } else {
160 284           hv_stores(e, "REMOTE_ADDR", SvREFCNT_inc_simple_NN(c->remote_addr));
161             }
162 297           hv_stores(e, "REMOTE_PORT", SvREFCNT_inc_simple_NN(c->remote_port));
163              
164             {
165 297           SV *scheme = feer_determine_url_scheme(aTHX_ c);
166 297 100         hv_stores(e, "psgi.url_scheme", scheme ? scheme : newSVpvs("http"));
167             }
168              
169 297           hv_stores(e, "CONTENT_LENGTH", newSViv(c->expected_cl));
170              
171             // Always provide psgi.input (for both PSGI and native handlers)
172             // For requests without body, it will be an empty stream (returns 0 on read)
173 297           hv_stores(e, "psgi.input", new_feer_conn_handle(aTHX_ c, 0));
174              
175 297 50         hv_stores(e, "psgi.multiprocess",
    50          
176             SvREFCNT_inc_simple_NN(c->server->multiprocess ? &PL_sv_yes : &PL_sv_no));
177              
178 297 100         if (c->cached_request_cb_is_psgi && c->server->psgix_io) {
    50          
179 93           SV *fake_fh = newSViv(c->fd); // fd value for psgix.io magic backing SV
180 93           SV *selfref = sv_2mortal(feer_conn_2sv(c));
181 93           sv_magicext(fake_fh, selfref, PERL_MAGIC_ext, &psgix_io_vtbl, NULL, 0);
182 93           hv_stores(e, "psgix.io", fake_fh);
183             }
184              
185 297 50         if (c->trailers) {
186 0           hv_stores(e, "psgix.h2.trailers", newRV_inc((SV*)c->trailers));
187             }
188              
189 297 100         if (c->proxy_tlvs) {
190 1           hv_stores(e, "psgix.proxy_tlvs", SvREFCNT_inc_simple_NN(c->proxy_tlvs));
191             }
192              
193 297 100         if (likely(!r->path)) feersum_set_path_and_query(aTHX_ r);
194 297           hv_stores(e, "PATH_INFO", SvREFCNT_inc_simple_NN(r->path));
195 297           hv_stores(e, "QUERY_STRING", SvREFCNT_inc_simple_NN(r->query));
196              
197 297           SV *cur_val = NULL; // tracks current header value for multi-value header merging
198 297           char *kbuf = header_key_buf; // use static buffer (pre-initialized with "HTTP_")
199              
200 1145 100         for (i=0; inum_headers; i++) {
201 848           struct phr_header *hdr = &(r->headers[i]);
202             // Note: obs-fold (hdr->name == NULL) is rejected at parse time per RFC 7230
203 894           if (unlikely(hdr->name_len == 14) &&
204 46           str_case_eq_fixed("content-length", hdr->name, 14))
205             {
206             // content length shouldn't show up as HTTP_CONTENT_LENGTH but
207             // as CONTENT_LENGTH in the env-hash.
208 46           continue;
209             }
210 812           else if (unlikely(hdr->name_len == 12) &&
211 10           str_case_eq_fixed("content-type", hdr->name, 12))
212             {
213 7           hv_stores(e, "CONTENT_TYPE", newSVpvn(hdr->value, hdr->value_len));
214 7           continue;
215             }
216              
217             // Skip headers with names too long for our buffer (defensive - should be
218             // rejected at parse time with 431, but guard against edge cases)
219 795 50         if (unlikely(hdr->name_len > MAX_HEADER_NAME_LEN)) {
220             trace("skipping oversized header name (len=%zu) on fd %d\n",
221             hdr->name_len, c->fd);
222 0           continue;
223             }
224              
225 795           size_t klen = 5+hdr->name_len;
226 795           char *key = kbuf + 5;
227 7509 100         for (j=0; jname_len; j++) {
228             // Use combined lookup table (uppercase + dash-to-underscore)
229 6714           *key++ = ascii_upper_dash[(unsigned char)hdr->name[j]];
230             }
231              
232 795           SV **fetched = hv_fetch(e, kbuf, klen, 1);
233             trace("adding header to env (fd %d) %.*s: %.*s\n",
234             c->fd, (int)klen, kbuf, (int)hdr->value_len, hdr->value);
235              
236             // hv_fetch with lval=1 should always succeed, but check for OOM safety
237 795 50         if (unlikely(fetched == NULL)) {
238             trace("hv_fetch returned NULL (OOM?) on fd %d\n", c->fd);
239 0           continue;
240             }
241 795           cur_val = *fetched; // track for multi-value header merging
242 795 100         if (unlikely(SvPOK(cur_val))) {
243             trace("... is multivalue\n");
244             // RFC 9113 §8.2.3: cookie uses "; ", others use ", "
245 2 50         const char *sep = (hdr->name_len == 6
246 0 0         && str_case_eq_fixed("cookie", hdr->name, 6)) ? "; " : ", ";
247 2           sv_catpvn(cur_val, sep, 2);
248 2           sv_catpvn(cur_val, hdr->value, hdr->value_len);
249             }
250             else {
251             // change from undef to a real value
252 793           sv_setpvn(cur_val, hdr->value, hdr->value_len);
253             }
254             }
255              
256             #ifdef FEERSUM_HAS_H2
257             /* Map :authority pseudo-header to HTTP_HOST for H2 streams (RFC 9113 §8.3.1).
258             * Only set if no regular Host header was already present. */
259             if (unlikely(c->is_h2_stream)) {
260             struct feer_h2_stream *stream = H2_STREAM_FROM_PC(c);
261             if (stream && stream->h2_authority && !hv_exists(e, "HTTP_HOST", 9)) {
262             STRLEN alen;
263             const char *aval = SvPV(stream->h2_authority, alen);
264             hv_stores(e, "HTTP_HOST", newSVpvn(aval, alen));
265             }
266             /* Extended CONNECT (RFC 8441): REQUEST_METHOD already set to GET
267             * above; add remaining H1-equivalent headers so existing PSGI
268             * WebSocket middleware works transparently.
269             * Matches HAProxy/nghttpx H2↔H1 upgrade translation. */
270             if (stream && stream->is_tunnel && stream->h2_protocol) {
271             STRLEN plen;
272             const char *pval = SvPV(stream->h2_protocol, plen);
273             hv_stores(e, "HTTP_UPGRADE", newSVpvn(pval, plen));
274             hv_stores(e, "HTTP_CONNECTION", newSVpvs("Upgrade"));
275             hv_stores(e, "psgix.h2.protocol", newSVpvn(pval, plen));
276             hv_stores(e, "psgix.h2.extended_connect", newSViv(1));
277             }
278             }
279             #endif
280              
281 297           return e;
282             }
283              
284             #define COPY_NORM_HEADER(_str) \
285             for (i = 0; i < r->num_headers; i++) {\
286             struct phr_header *hdr = &(r->headers[i]);\
287             /* Invariant: obs-fold and oversized names already rejected at parse time */\
288             if (unlikely(hdr->name_len > MAX_HEADER_NAME_LEN)) continue; /* defense-in-depth */\
289             char *k = kbuf;\
290             for (j = 0; j < hdr->name_len; j++) { char n = hdr->name[j]; *k++ = _str; }\
291             SV** val = hv_fetch(e, kbuf, hdr->name_len, 1);\
292             if (unlikely(!val)) continue; /* OOM safety */\
293             if (unlikely(SvPOK(*val))) {\
294             const char *sep = (hdr->name_len == 6\
295             && str_case_eq_fixed("cookie", hdr->name, 6)) ? "; " : ", ";\
296             sv_catpvn(*val, sep, 2);\
297             sv_catpvn(*val, hdr->value, hdr->value_len);\
298             } else {\
299             sv_setpvn(*val, hdr->value, hdr->value_len);\
300             }\
301             }\
302             break;
303              
304             // Static buffer for feersum_env_headers (reuses header_key_buf area after HTTP_ prefix)
305             static HV*
306 7           feersum_env_headers(pTHX_ struct feer_req *r, int norm)
307             {
308             size_t i; size_t j; HV* e;
309 7           e = newHV();
310             // Pre-allocate hash buckets based on expected header count to avoid rehashing
311 7 50         if (r->num_headers > 0)
312 7           hv_ksplit(e, r->num_headers);
313 7           char *kbuf = header_key_buf + 5; // reuse static buffer, skip the "HTTP_" prefix area
314 7           switch (norm) {
315 1           case HEADER_NORM_SKIP:
316 26 50         COPY_NORM_HEADER(n)
    100          
    50          
    50          
    0          
    0          
    100          
317 2           case HEADER_NORM_LOCASE:
318 71 50         COPY_NORM_HEADER(ascii_lower[(unsigned char)n])
    100          
    50          
    100          
    50          
    100          
    100          
319 1           case HEADER_NORM_UPCASE:
320 26 50         COPY_NORM_HEADER(ascii_upper[(unsigned char)n])
    100          
    50          
    50          
    0          
    0          
    100          
321 2           case HEADER_NORM_LOCASE_DASH:
322 82 50         COPY_NORM_HEADER(ascii_lower_dash[(unsigned char)n])
    100          
    50          
    50          
    0          
    0          
    100          
323 1           case HEADER_NORM_UPCASE_DASH:
324 26 50         COPY_NORM_HEADER(ascii_upper_dash[(unsigned char)n])
    100          
    50          
    50          
    0          
    0          
    100          
325 0           default:
326 0           break;
327             }
328 7           return e;
329             }
330              
331             INLINE_UNLESS_DEBUG static SV*
332 10           feersum_env_header(pTHX_ struct feer_req *r, SV *name)
333             {
334 10           SV *result = NULL;
335             STRLEN nlen;
336 10           const char *np = SvPV(name, nlen);
337             /* RFC 9113 §8.2.3: cookie headers joined with "; ", others with ", " */
338 10 100         const bool is_cookie = (nlen == 6 && str_case_eq_fixed("cookie", np, 6));
    100          
339 52 100         for (size_t i = 0; i < r->num_headers; i++) {
340 42           struct phr_header *hdr = &(r->headers[i]);
341 42 100         if (unlikely(hdr->name_len == nlen
    100          
342             && str_case_eq_both(np, hdr->name, nlen))) {
343 12 100         if (likely(!result)) {
344 10           result = newSVpvn(hdr->value, hdr->value_len);
345             } else {
346 2 100         sv_catpvn(result, is_cookie ? "; " : ", ", 2);
347 2           sv_catpvn(result, hdr->value, hdr->value_len);
348             }
349             }
350             }
351 10 50         return result ? result : &PL_sv_undef;
352             }
353              
354             /* Build an IO::Socket::INET wrapping `fd` via Perl helper Feersum::Connection::_raw,
355             * then stash a back-ref to c->self in the glob's scalar slot so the IO handle
356             * keeps the connection alive. Returns the blessed RV (refcount 1). Croaks with
357             * "Failed to create IO handle..." on any failure; the caller is then
358             * responsible for closing the fd it passed in. */
359             static SV *
360 12           feer_make_io_handle(pTHX_ struct feer_conn *c, int fd, const char *who)
361             {
362 12           dSP;
363 12           SV *sv = newSViv(fd);
364              
365 12           ENTER;
366 12           SAVETMPS;
367 12 50         PUSHMARK(SP);
368 12 50         XPUSHs(sv);
369 12 50         mXPUSHs(newSViv(fd));
370 12           PUTBACK;
371              
372 12           call_pv("Feersum::Connection::_raw", G_VOID|G_DISCARD|G_EVAL);
373 12           SPAGAIN;
374              
375 12 50         if (unlikely(SvTRUE(ERRSV))) {
    50          
376 0 0         FREETMPS; LEAVE;
377 0           SvREFCNT_dec(sv);
378 0 0         croak("Failed to create %s IO handle: %-p", who, ERRSV);
379             }
380 12 50         if (unlikely(!SvROK(sv))) {
381 0 0         FREETMPS; LEAVE;
382 0           SvREFCNT_dec(sv);
383 0           croak("Failed to create %s IO handle: new_from_fd returned undef", who);
384             }
385              
386 12           SV *io_glob = SvRV(sv);
387 12           GvSV(io_glob) = newRV_inc(c->self);
388              
389 12 50         FREETMPS;
390 12           LEAVE;
391 12           return sv;
392             }
393              
394             static SV*
395 13           feersum_env_io(pTHX_ struct feer_conn *c)
396             {
397             // Prevent double-call and misuse after response started
398 13 100         if (unlikely(c->io_taken))
399 1           croak("io() already called on this connection");
400 12 50         if (unlikely(c->responding != RESPOND_NOT_STARTED))
401 0           croak("io() cannot be called after send_response/start_response");
402              
403             trace("feersum_env_io for fd=%d\n", c->fd);
404              
405             #ifdef FEERSUM_HAS_H2
406             /* H2 tunnel: auto-accept, create socketpair, expose sv[1] as IO handle */
407             if (c->is_h2_stream) {
408             struct feer_h2_stream *stream = H2_STREAM_FROM_PC(c);
409             if (!stream || !stream->is_tunnel)
410             croak("io() is not supported on regular HTTP/2 streams");
411             h2_tunnel_auto_accept(aTHX_ c, stream);
412             /* Re-fetch stream: auto_accept → session_send may have freed it */
413             stream = H2_STREAM_FROM_PC(c);
414             if (!stream)
415             croak("io() tunnel: stream freed during auto-accept");
416             feer_h2_setup_tunnel(aTHX_ stream);
417             if (!stream->tunnel_established)
418             croak("Failed to create tunnel socketpair");
419              
420             SV *sv = feer_make_io_handle(aTHX_ c, stream->tunnel_sv1, "tunnel");
421             /* sv[1] now owned by the IO handle */
422             stream->tunnel_sv1 = -1;
423             c->io_taken = 1;
424             return sv;
425             }
426             #endif
427              
428             #ifdef FEERSUM_HAS_TLS
429             /* TLS tunnel: create socketpair relay for bidirectional I/O over TLS */
430 12 100         if (c->tls) {
431 9           feer_tls_setup_tunnel(c);
432 9 50         if (!c->tls_tunnel)
433 0           croak("Failed to create TLS tunnel socketpair");
434              
435 9           SV *sv = feer_make_io_handle(aTHX_ c, c->tls_tunnel_sv1, "TLS tunnel");
436             /* sv[1] now owned by the IO handle */
437 9           c->tls_tunnel_sv1 = -1;
438 9           c->io_taken = 1;
439 9           stop_read_timer(c);
440 9           stop_write_timer(c);
441             /* Keep read watcher active: TLS reads relay to tunnel */
442 9           return sv;
443             }
444             #endif
445              
446 3           SV *sv = feer_make_io_handle(aTHX_ c, c->fd, "raw socket");
447 3           SV *io_glob = SvRV(sv);
448              
449             // Push any remaining rbuf data into the socket buffer
450 3 50         if (likely(c->rbuf && SvOK(c->rbuf) && SvCUR(c->rbuf))) {
    50          
    50          
    100          
451             STRLEN rbuf_len;
452 1           const char *rbuf_ptr = SvPV(c->rbuf, rbuf_len);
453 1           IO *io = GvIOp(io_glob);
454 1 50         if (io) {
455 1           SSize_t pushed = PerlIO_unread(IoIFP(io), (const void *)rbuf_ptr, rbuf_len);
456 1 50         if (likely(pushed == (SSize_t)rbuf_len)) {
457 1           SvCUR_set(c->rbuf, 0);
458 1           *SvPVX(c->rbuf) = '\0';
459 0 0         } else if (pushed > 0) {
460 0           sv_chop(c->rbuf, rbuf_ptr + pushed);
461 0           trouble("PerlIO_unread partial in io(): %zd of %"Sz_uf" bytes fd=%d\n",
462             pushed, (Sz)rbuf_len, c->fd);
463             } else {
464 0           trouble("PerlIO_unread failed in io() fd=%d\n", c->fd);
465             }
466             }
467             }
468              
469             // Stop Feersum's watchers; user now owns the socket
470 3           stop_read_watcher(c);
471 3           stop_read_timer(c);
472 3           stop_write_timer(c);
473             // don't stop write watcher in case there's outstanding data
474              
475 3           c->io_taken = 1;
476 3           return sv;
477             }
478              
479             static SSize_t
480 4           feersum_return_from_io(pTHX_ struct feer_conn *c, SV *io_sv, const char *func_name)
481             {
482             #ifdef FEERSUM_HAS_H2
483             if (unlikely(c->is_h2_stream))
484             croak("%s: not supported on HTTP/2 streams", func_name);
485             #endif
486             #ifdef FEERSUM_HAS_TLS
487 4 100         if (c->tls_tunnel)
488 1           croak("%s: not supported on TLS tunnel connections "
489             "(io() over TLS uses a socketpair relay that cannot be returned)",
490             func_name);
491             #endif
492              
493 3 50         if (!SvROK(io_sv) || !isGV_with_GP(SvRV(io_sv)))
    50          
    50          
    0          
494 0           croak("%s requires a filehandle", func_name);
495              
496 3           GV *gv = (GV *)SvRV(io_sv);
497 3 50         IO *io = GvIO(gv);
    50          
    0          
    50          
498 3 50         if (!io || !IoIFP(io))
    50          
499 0           croak("%s: invalid filehandle", func_name);
500              
501 3           PerlIO *fp = IoIFP(io);
502              
503             // Check if there's buffered data to pull back
504 3           SSize_t cnt = PerlIO_get_cnt(fp);
505 3 50         if (cnt > 0) {
506             // Get pointer to buffered data
507             // Note: ptr remains valid until next PerlIO operation on fp.
508             // sv_catpvn doesn't touch fp, so this is safe.
509 0           STDCHAR *ptr = PerlIO_get_ptr(fp);
510 0 0         if (ptr) {
511             // Ensure we have an rbuf
512 0 0         if (!c->rbuf)
513 0           c->rbuf = newSV(READ_BUFSZ);
514              
515             // Append buffered data to feersum's rbuf
516 0           sv_catpvn(c->rbuf, (const char *)ptr, cnt);
517              
518             // Mark buffer as consumed (must happen before any other PerlIO ops)
519 0           PerlIO_set_ptrcnt(fp, ptr + cnt, 0);
520              
521             trace("pulled %zd bytes back to feersum fd=%d\n", (size_t)cnt, c->fd);
522             }
523             }
524              
525             /* Reset connection state for next request (like keepalive reset) */
526 3           change_responding_state(c, RESPOND_NOT_STARTED);
527 3           change_receiving_state(c, RECEIVE_HEADERS);
528 3           c->expected_cl = 0;
529 3           c->received_cl = 0;
530 3           c->io_taken = 0;
531 3           free_request(c);
532              
533 3 50         if (c->rbuf && cnt <= 0)
    50          
534 3           SvCUR_set(c->rbuf, 0);
535              
536 3           start_read_watcher(c);
537 3           restart_read_timer(c);
538 3           restart_header_timer(c);
539              
540 3           return cnt > 0 ? cnt : 0;
541             }
542              
543             /* Scan response status message and headers for bytes that would let an app
544             * inject extra headers or a second response (CWE-113 response splitting).
545             * Returns NULL on success; a literal error string on failure. Caller decides
546             * whether to croak() (safe inside a Perl handler call_sv G_EVAL frame) or set
547             * ERRSV and call_died (PSGI dispatch path is not in a G_EVAL). */
548             static const char *
549 603           feersum_check_response_framing(pTHX_ SV *message, AV *headers)
550             {
551 603 100         if (SvPOK(message)) {
552             STRLEN mlen;
553 87           const char *mp = SvPV(message, mlen);
554 616 100         for (STRLEN k = 0; k < mlen; k++)
555 531 100         if (unlikely(mp[k] == '\r' || mp[k] == '\n'))
    100          
556 2           return "Status message contains CR or LF";
557             }
558 601           I32 avl = av_len(headers);
559 601           SV **ary = AvARRAY(headers);
560 1369 100         for (I32 j = 0; j < avl; j += 2) {
561 777           SV *hdr = ary[j];
562 777           SV *val = ary[j+1];
563 777 50         if (!hdr || !SvOK(hdr) || !val || !SvOK(val))
    50          
    50          
    50          
564 0           continue;
565             STRLEN hlen, vlen;
566 777           const char *hp = SvPV(hdr, hlen);
567 777           const char *vp = SvPV(val, vlen);
568 9977 100         for (STRLEN k = 0; k < hlen; k++)
569 9203 100         if (unlikely(hp[k] == '\r' || hp[k] == '\n' || hp[k] == ':'))
    100          
    100          
    100          
570 9           return "Response header name contains CR, LF, or colon";
571 7302 100         for (STRLEN k = 0; k < vlen; k++)
572 6534 100         if (unlikely(vp[k] == '\r' || vp[k] == '\n'))
    100          
573 6           return "Response header value contains CR or LF";
574             }
575 592           return NULL;
576             }
577              
578             static void
579 526           feersum_start_response (pTHX_ struct feer_conn *c, SV *message, AV *headers,
580             int streaming)
581             {
582             const char *ptr;
583              
584             trace("start_response fd=%d streaming=%d\n", c->fd, streaming);
585              
586 526 50         if (unlikely(!SvOK(message) || !(SvIOK(message) || SvPOK(message)))) {
    100          
    50          
    50          
587 0           croak("Must define an HTTP status code or message");
588             }
589              
590 526           I32 avl = av_len(headers);
591 526 50         if (unlikely((avl+1) % 2 == 1)) {
592 0           croak("expected even-length array, got %d", avl+1);
593             }
594              
595             /* CR/LF/colon check. Reached from XS send_response/start_streaming via the
596             * user handler (call_sv G_EVAL catches this croak) and from the PSGI
597             * dispatcher (which pre-validates with feersum_check_response_framing and
598             * call_died on failure, so this path is a defense-in-depth no-op there). */
599 526           const char *err = feersum_check_response_framing(aTHX_ message, headers);
600 526 100         if (unlikely(err)) croak("%s", err);
601              
602             #ifdef FEERSUM_HAS_H2
603             if (unlikely(c->is_h2_stream)) {
604             feersum_h2_start_response(aTHX_ c, message, headers, streaming);
605             return;
606             }
607             #endif
608              
609 516 100         if (unlikely(c->responding != RESPOND_NOT_STARTED))
610 4           croak("already responding?!");
611 512 100         change_responding_state(c, streaming ? RESPOND_STREAMING : RESPOND_NORMAL);
612              
613             // int or 3 chars? use a stock message
614 512           UV code = 0;
615 512 100         if (SvIOK(message))
616 434           code = SvIV(message);
617             else {
618 78           STRLEN mlen = SvCUR(message);
619 78           const int numtype = grok_number(SvPVX_const(message), mlen > 3 ? 3 : mlen, &code);
620 78 50         if (unlikely(numtype != IS_NUMBER_IN_UV))
621 0           code = 0;
622             }
623             trace2("starting response fd=%d code=%"UVuf"\n",c->fd,code);
624              
625 512 50         if (unlikely(!code))
626 0           croak("first parameter is not a number or doesn't start with digits");
627              
628             if (FEERSUM_RESP_START_ENABLED()) {
629             FEERSUM_RESP_START(c->fd, (int)code);
630             }
631              
632             // for PSGI it's always just an IV so optimize for that
633 512 100         if (likely(!SvPOK(message) || SvCUR(message) == 3)) {
    100          
634             // Use cached status SVs for common codes to avoid newSVpvf overhead
635 435           switch (code) {
636 431           case 200: message = status_200; break;
637 0           case 201: message = status_201; break;
638 3           case 204: message = status_204; break;
639 0           case 301: message = status_301; break;
640 0           case 302: message = status_302; break;
641 1           case 304: message = status_304; break;
642 0           case 400: message = status_400; break;
643 0           case 404: message = status_404; break;
644 0           case 500: message = status_500; break;
645 0           default:
646 0           ptr = http_code_to_msg(code);
647 0           message = sv_2mortal(newSVpvf("%"UVuf" %s",code,ptr));
648 0           break;
649             }
650             }
651              
652 512 100         c->auto_cl = http_status_no_body(code) ? 0 : 1;
    50          
    100          
    50          
    50          
653              
654             // Build entire header block into a single buffer to minimize iovecs.
655             // Pre-calculate total size to avoid reallocation.
656             STRLEN msg_len;
657 512           const char *msg_ptr = SvPV(message, msg_len);
658              
659             // First pass: calculate total header size and detect Content-Length.
660 512           bool has_content_length = 0;
661 512           STRLEN hdr_total = 9 + msg_len + 2; // "HTTP/1.x " + message + CRLF
662 512           SV **ary = AvARRAY(headers);
663 1174 100         for (I32 i = 0; i < avl; i += 2) {
664 662           SV *hdr = ary[i];
665 662           SV *val = ary[i+1];
666 662 50         if (unlikely(!hdr || !SvOK(hdr) || !val || !SvOK(val)))
    50          
    50          
    50          
    50          
    50          
667 68           continue;
668             STRLEN hlen, vlen;
669 662           const char *hp = SvPV(hdr, hlen);
670 662 100         if (unlikely(hlen == 14) && str_case_eq_fixed("content-length", hp, 14)) {
    50          
671 78 100         if (likely(c->auto_cl) && !streaming)
    100          
672 68           continue;
673 10           has_content_length = 1;
674             }
675 594           (void)SvPV(val, vlen);
676 594           hdr_total += hlen + 2 + vlen + 2; // key + ": " + value + CRLF
677             }
678             // HTTP/1.0 streaming with a body but no Content-Length: the only way to
679             // signal end-of-body is Connection: close. No-body statuses (auto_cl=0)
680             // are exempt — RFC 7230 §3.3.3 rule 1 says clients MUST NOT expect a body
681             // for 1xx/204/304 regardless of framing headers, so keepalive is safe.
682 512 100         if (streaming && c->is_keepalive && !has_content_length
    100          
    100          
683 6 100         && !c->is_http11 && c->auto_cl)
    50          
684 1           c->is_keepalive = 0;
685              
686             // Date, Connection, Transfer-Encoding, terminal CRLF
687 512 100         if (likely(c->is_http11))
688 496 100         hdr_total += DATE_HEADER_LENGTH + (!c->is_keepalive ? 19 : 0);
689 16 100         else if (c->is_keepalive)
690 1           hdr_total += 24; // "Connection: keep-alive\r\n"
691 512 100         if (streaming) {
692 59 100         if (c->is_http11 && !has_content_length && c->auto_cl)
    100          
    100          
693 40           hdr_total += 30; // "Transfer-Encoding: chunked\r\n\r\n"
694             else
695 19           hdr_total += 2; // terminal CRLF
696             }
697              
698             // Second pass: build the header buffer
699 512           SV *hdr_sv = newSV(hdr_total + 1);
700 512           SvPOK_on(hdr_sv);
701 512           char *p = SvPVX(hdr_sv);
702 512           STRLEN hdr_alloc = hdr_total;
703              
704             // Status line
705 512 100         const char *ver = c->is_http11 ? "HTTP/1.1 " : "HTTP/1.0 ";
706 512           memcpy(p, ver, 9); p += 9;
707 512           memcpy(p, msg_ptr, msg_len); p += msg_len;
708 512           *p++ = '\r'; *p++ = '\n';
709              
710             // Response headers
711 1174 100         for (I32 i = 0; i < avl; i += 2) {
712 662           SV *hdr = ary[i];
713 662           SV *val = ary[i+1];
714 662 50         if (unlikely(!hdr || !SvOK(hdr) || !val || !SvOK(val)))
    50          
    50          
    50          
    50          
    50          
715 68           continue;
716             STRLEN hlen, vlen;
717 662           const char *hp = SvPV(hdr, hlen);
718 662 100         if (unlikely(hlen == 14) && str_case_eq_fixed("content-length", hp, 14)) {
    50          
719 78 100         if (likely(c->auto_cl) && !streaming)
    100          
720 68           continue;
721             }
722 594           const char *vp = SvPV(val, vlen);
723 594           STRLEN need = hlen + 2 + vlen + 2;
724             // Guard against magical SVs returning longer strings than pass 1
725 594 50         if (unlikely((p - SvPVX(hdr_sv)) + need > hdr_alloc)) {
726 0           STRLEN pos = p - SvPVX(hdr_sv);
727 0           hdr_alloc = pos + need + 128; // slack covers tail (Date+Connection+TE ≤ 86 bytes)
728 0 0         SvGROW(hdr_sv, hdr_alloc + 1);
    0          
729 0           p = SvPVX(hdr_sv) + pos;
730             }
731 594           memcpy(p, hp, hlen); p += hlen;
732 594           *p++ = ':'; *p++ = ' ';
733 594           memcpy(p, vp, vlen); p += vlen;
734 594           *p++ = '\r'; *p++ = '\n';
735             }
736              
737             // Date and Connection headers
738 512 100         if (likely(c->is_http11)) {
739 496           memcpy(p, DATE_BUF, DATE_HEADER_LENGTH); p += DATE_HEADER_LENGTH;
740 496 100         if (!c->is_keepalive) {
741 355           memcpy(p, "Connection: close\r\n", 19); p += 19;
742             }
743 16 100         } else if (c->is_keepalive) {
744 1           memcpy(p, "Connection: keep-alive\r\n", 24); p += 24;
745             }
746              
747 512 100         if (streaming) {
748 59 100         if (c->is_http11 && !has_content_length && c->auto_cl) {
    100          
    100          
749 40           memcpy(p, "Transfer-Encoding: chunked\r\n\r\n", 30); p += 30;
750 40           c->use_chunked = 1;
751             }
752             else {
753 19           *p++ = '\r'; *p++ = '\n';
754 19           c->use_chunked = 0;
755             }
756             }
757              
758 512           SvCUR_set(hdr_sv, p - SvPVX(hdr_sv));
759 512           *p = '\0';
760              
761             // Add as a single iovec entry
762             {
763 512           struct iomatrix *m = next_iomatrix(c);
764 512           unsigned idx = m->count++;
765 512           m->iov[idx].iov_base = SvPVX(hdr_sv);
766 512           m->iov[idx].iov_len = SvCUR(hdr_sv);
767 512           m->sv[idx] = hdr_sv;
768 512           c->wbuf_len += SvCUR(hdr_sv);
769             }
770              
771             // For streaming responses, start writing headers immediately.
772             // For non-streaming (RESPOND_NORMAL), feersum_write_whole_body will
773             // call conn_write_ready after the body is buffered. This is critical
774             // because conn_write_ready triggers immediate writes and would
775             // prematurely finish the response before body is ready.
776 512 100         if (streaming)
777 59           conn_write_ready(c);
778 512           }
779              
780             static size_t
781 453           feersum_write_whole_body (pTHX_ struct feer_conn *c, SV *body)
782             {
783             size_t RETVAL;
784 453           bool body_is_string = 0;
785             STRLEN cur;
786              
787 453 50         if (c->responding != RESPOND_NORMAL)
788 0           croak("can't use write_whole_body when in streaming mode");
789              
790             #ifdef FEERSUM_HAS_H2
791             if (unlikely(c->is_h2_stream)) {
792             /* H2 streams use nghttp2 submit_response, not wbuf */
793             SV *body_sv;
794             if (!SvOK(body)) {
795             body_sv = sv_2mortal(newSVpvs(""));
796             } else if (SvROK(body)) {
797             SV *refd = SvRV(body);
798             if (SvOK(refd) && !SvROK(refd)) {
799             body_sv = refd;
800             } else if (SvTYPE(refd) == SVt_PVAV) {
801             AV *ab = (AV*)refd;
802             body_sv = sv_2mortal(newSVpvs(""));
803             I32 amax = av_len(ab);
804             for (I32 i = 0; i <= amax; i++) {
805             SV *sv = fetch_av_normal(aTHX_ ab, i);
806             if (sv) sv_catsv(body_sv, sv);
807             }
808             } else {
809             croak("body must be a scalar, scalar reference or array reference");
810             }
811             } else {
812             body_sv = body;
813             }
814             return feersum_h2_write_whole_body(aTHX_ c, body_sv);
815             }
816             #endif
817              
818 453 50         if (!SvOK(body)) {
819 0           body = sv_2mortal(newSVpvs(""));
820 0           body_is_string = 1;
821             }
822 453 100         else if (SvROK(body)) {
823 334           SV *refd = SvRV(body);
824 334 100         if (SvOK(refd) && !SvROK(refd)) {
    50          
825 175           body = refd;
826 175           body_is_string = 1;
827             }
828 159 50         else if (SvTYPE(refd) != SVt_PVAV) {
829 0           croak("body must be a scalar, scalar reference or array reference");
830             }
831             }
832             else {
833 119           body_is_string = 1;
834             }
835              
836             // For array bodies with a single element, extract the scalar for the fast path
837 453           SV *body_scalar = NULL;
838 453 100         if (body_is_string) {
839 294           body_scalar = body;
840             } else {
841 159           AV *abody = (AV*)SvRV(body);
842 159 100         if (av_len(abody) == 0) {
843 140           SV *elem = fetch_av_normal(aTHX_ abody, 0);
844 140 50         if (elem) body_scalar = elem;
845             }
846             }
847              
848             // Optimization: for small scalar bodies with auto_cl, append Content-Length
849             // and body directly to the header iomatrix entry, producing a single iovec
850             // for the entire HTTP response → write() fast path.
851             // For large bodies (>4KB), keep them as a separate iovec to avoid memcpy;
852             // writev with 2-3 iovecs is fine and avoids duplicating the body buffer.
853 453 100         if (likely(c->auto_cl) && body_scalar && c->wbuf_rinq) {
    100          
    50          
854 433           struct iomatrix *m = (struct iomatrix *)c->wbuf_rinq->prev->ref;
855 433           unsigned last = m->count - 1;
856 433           SV *hdr_sv = m->sv[last];
857              
858             // Guard: verify the tail iomatrix entry is the writable header SV
859             // from feersum_start_response (not a const entry or NULL).
860 433 50         if (unlikely(!hdr_sv || SvREADONLY(hdr_sv)))
    50          
861 0           goto whole_body_slow_path;
862              
863             STRLEN body_len;
864 433           const char *body_ptr = SvPV(body_scalar, body_len);
865             char cl_buf[48];
866 433           int cl_len = format_content_length(cl_buf, body_len);
867 433           STRLEN hdr_cur = SvCUR(hdr_sv);
868              
869 433 100         if (likely(body_len <= 4096)) {
870             // Small body: merge headers + CL + body into one buffer → write()
871 431           STRLEN total = hdr_cur + cl_len + body_len;
872 431 50         SvGROW(hdr_sv, total + 1);
    50          
873 431           char *p = SvPVX(hdr_sv) + hdr_cur;
874 431           memcpy(p, cl_buf, cl_len); p += cl_len;
875 431           memcpy(p, body_ptr, body_len); p += body_len;
876 431           *p = '\0';
877 431           SvCUR_set(hdr_sv, total);
878 431           m->iov[last].iov_len = total;
879 431           c->wbuf_len += cl_len + body_len;
880             } else {
881             // Large body: append CL to header buffer, body as separate iovec
882 2 50         SvGROW(hdr_sv, hdr_cur + cl_len + 1);
    50          
883 2           memcpy(SvPVX(hdr_sv) + hdr_cur, cl_buf, cl_len);
884 2           SvCUR_set(hdr_sv, hdr_cur + cl_len);
885 2           SvPVX(hdr_sv)[hdr_cur + cl_len] = '\0';
886 2           m->iov[last].iov_len = hdr_cur + cl_len;
887 2           c->wbuf_len += cl_len;
888 2           add_sv_to_wbuf(c, body_scalar);
889             }
890 433           m->iov[last].iov_base = SvPVX(hdr_sv);
891 433           RETVAL = body_len;
892             }
893 20           else { whole_body_slow_path:;
894             SV *cl_sv; // content-length future
895             struct iovec *cl_iov;
896 20 100         if (likely(c->auto_cl))
897 16           add_placeholder_to_wbuf(c, &cl_sv, &cl_iov);
898             else
899 4           add_crlf_to_wbuf(c);
900              
901             // RFC 7230 §3.3: 1xx/204/205/304 MUST NOT have a message body.
902             // Skip body writes when auto_cl is 0 (no-body status codes).
903 20 100         if (unlikely(!c->auto_cl)) {
904 4           RETVAL = 0;
905             }
906 16 50         else if (body_is_string) {
907 0           cur = add_sv_to_wbuf(c,body);
908 0           RETVAL = cur;
909             }
910             else {
911 16           AV *abody = (AV*)SvRV(body);
912 16           I32 amax = av_len(abody);
913 16           RETVAL = 0;
914 51 100         for (I32 i = 0; i <= amax; i++) {
915 35           SV *sv = fetch_av_normal(aTHX_ abody, i);
916 35 100         if (unlikely(!sv)) continue;
917 34           cur = add_sv_to_wbuf(c,sv);
918             trace("body part i=%d sv=%p cur=%"Sz_uf"\n", i, sv, (Sz)cur);
919 34           RETVAL += cur;
920             }
921             }
922              
923 20 100         if (likely(c->auto_cl)) {
924             char cl_buf[48];
925 16           int cl_len = format_content_length(cl_buf, RETVAL);
926 16           sv_setpvn(cl_sv, cl_buf, cl_len);
927 16           update_wbuf_placeholder(c, cl_sv, cl_iov);
928             }
929             }
930              
931 453           change_responding_state(c, RESPOND_SHUTDOWN);
932 453           conn_write_ready(c);
933 453           return RETVAL;
934             }
935              
936             static void
937 16           call_died (pTHX_ struct feer_conn *c, const char *cb_type)
938             {
939 16           dSP;
940             #if DEBUG >= 1
941             trace("An error was thrown in the %s callback: %-p\n",cb_type,ERRSV);
942             #endif
943 16 50         PUSHMARK(SP);
944 16 50         mXPUSHs(newSVsv(ERRSV));
    50          
945 16           PUTBACK;
946 16           call_pv("Feersum::DIED", G_DISCARD|G_EVAL|G_VOID);
947 16           SPAGAIN;
948              
949 16           respond_with_server_error(c, "Request handler exception\n", 500);
950 16 50         sv_setsv(ERRSV, &PL_sv_undef);
951 16           }
952              
953             static void
954 17           feersum_start_psgi_streaming(pTHX_ struct feer_conn *c, SV *streamer)
955             {
956 17           dSP;
957 17           ENTER;
958 17           SAVETMPS;
959 17 50         PUSHMARK(SP);
960 17 50         mXPUSHs(feer_conn_2sv(c));
961 17 50         XPUSHs(streamer);
962 17           PUTBACK;
963 17           call_method("_initiate_streaming_psgi", G_DISCARD|G_EVAL|G_VOID);
964 17           SPAGAIN;
965 17 50         if (unlikely(SvTRUE(ERRSV))) {
    100          
966 1           call_died(aTHX_ c, "PSGI stream initiator");
967             }
968 17           PUTBACK;
969 17 50         FREETMPS;
970 17           LEAVE;
971 17           }
972              
973             static void
974 96           feersum_handle_psgi_response(
975             pTHX_ struct feer_conn *c, SV *ret, bool can_recurse)
976             {
977 96 50         if (unlikely(!SvOK(ret) || !SvROK(ret))) {
    50          
978 0 0         sv_setpvs(ERRSV, "Invalid PSGI response (expected reference)");
979 0           call_died(aTHX_ c, "PSGI request");
980 0           return;
981             }
982              
983 96 50         if (unlikely(!IsArrayRef(ret))) {
    100          
984 17 50         if (likely(can_recurse)) {
985             trace("PSGI response non-array, c=%p ret=%p\n", c, ret);
986 17           feersum_start_psgi_streaming(aTHX_ c, ret);
987             }
988             else {
989 0 0         sv_setpvs(ERRSV, "PSGI attempt to recurse in a streaming callback");
990 0           call_died(aTHX_ c, "PSGI request");
991             }
992 17           return;
993             }
994              
995 79           AV *psgi_triplet = (AV*)SvRV(ret);
996 79 50         if (unlikely(av_len(psgi_triplet)+1 != 3)) {
997 0 0         sv_setpvs(ERRSV, "Invalid PSGI array response (expected triplet)");
998 0           call_died(aTHX_ c, "PSGI request");
999 0           return;
1000             }
1001              
1002             trace("PSGI response triplet, c=%p av=%p\n", c, psgi_triplet);
1003             // we know there's three elems so *should* be safe to de-ref
1004 79           SV **msg_p = av_fetch(psgi_triplet,0,0);
1005 79           SV **hdrs_p = av_fetch(psgi_triplet,1,0);
1006 79           SV **body_p = av_fetch(psgi_triplet,2,0);
1007 79 50         if (unlikely(!msg_p || !hdrs_p || !body_p)) {
    50          
    50          
    50          
1008 0 0         sv_setpvs(ERRSV, "Invalid PSGI array response (NULL element)");
1009 0           call_died(aTHX_ c, "PSGI request");
1010 0           return;
1011             }
1012 79           SV *msg = *msg_p;
1013 79           SV *hdrs = *hdrs_p;
1014 79           SV *body = *body_p;
1015              
1016             AV *headers;
1017 79 50         if (IsArrayRef(hdrs))
    50          
1018 79           headers = (AV*)SvRV(hdrs);
1019             else {
1020 0 0         sv_setpvs(ERRSV, "PSGI Headers must be an array-ref");
1021 0           call_died(aTHX_ c, "PSGI request");
1022 0           return;
1023             }
1024              
1025             /* Pre-validate everything feersum_start_response would croak() on. The
1026             * PSGI dispatch path runs outside any G_EVAL frame (call_request_callback
1027             * already exited its call_sv G_EVAL before reaching here), so a croak
1028             * would unwind through the libev callback into Perl space and crash the
1029             * worker. Convert each croak case into call_died here. */
1030 79 50         if (unlikely(!SvOK(msg) || !(SvIOK(msg) || SvPOK(msg)))) {
    100          
    50          
    50          
1031 0 0         sv_setpvs(ERRSV, "PSGI status must be a number or status string");
1032 0           call_died(aTHX_ c, "PSGI request");
1033 0           return;
1034             }
1035 79 100         if (unlikely((av_len(headers) + 1) % 2 == 1)) {
1036 1 50         sv_setpvs(ERRSV, "PSGI headers must be an even-length array");
1037 1           call_died(aTHX_ c, "PSGI request");
1038 1           return;
1039             }
1040 78 100         if (SvPOK(msg) && !SvIOK(msg)) {
    50          
1041 1           UV code = 0;
1042 1           STRLEN mlen = SvCUR(msg);
1043 1 50         if (grok_number(SvPVX_const(msg), mlen > 3 ? 3 : mlen, &code) != IS_NUMBER_IN_UV
1044 0 0         || code == 0)
1045             {
1046 1 50         sv_setpvs(ERRSV, "PSGI status must start with a positive number");
1047 1           call_died(aTHX_ c, "PSGI request");
1048 1           return;
1049             }
1050             }
1051             {
1052 77           const char *err = feersum_check_response_framing(aTHX_ msg, headers);
1053 77 100         if (unlikely(err)) {
1054 1 50         sv_setpv(ERRSV, err);
1055 1           call_died(aTHX_ c, "PSGI request");
1056 1           return;
1057             }
1058             }
1059              
1060 76 50         if (likely(IsArrayRef(body))) {
    100          
1061 71           feersum_start_response(aTHX_ c, msg, headers, 0);
1062 71           feersum_write_whole_body(aTHX_ c, body);
1063             }
1064 5 50         else if (likely(SvROK(body))) { // probably an IO::Handle-like object
1065 5           feersum_start_response(aTHX_ c, msg, headers, 1);
1066             #ifdef FEERSUM_HAS_H2
1067             if (unlikely(c->is_h2_stream)) {
1068             /* H2: drain the IO::Handle synchronously since there is no
1069             * per-stream write watcher; nghttp2 handles flow control. */
1070             pump_h2_io_handle(aTHX_ c, body);
1071             } else
1072             #endif
1073             {
1074 5           c->poll_write_cb = newSVsv(body);
1075 5           c->poll_write_cb_is_io_handle = 1;
1076 5           conn_write_ready(c);
1077             }
1078             }
1079             else {
1080 0 0         sv_setpvs(ERRSV, "Expected PSGI array-ref or IO::Handle-like body");
1081 0           call_died(aTHX_ c, "PSGI request");
1082 0           return;
1083             }
1084             }
1085              
1086             static int
1087 61           feersum_close_handle (pTHX_ struct feer_conn *c, bool is_writer)
1088             {
1089             int RETVAL;
1090 61 100         if (is_writer) {
1091             trace("close writer fd=%d, c=%p, refcnt=%d\n", c->fd, c, SvREFCNT(c->self));
1092 54 100         if (c->poll_write_cb) {
1093 1           SV *tmp = c->poll_write_cb;
1094 1           c->poll_write_cb = NULL; // NULL before dec: prevents re-entrant double-free
1095 1           c->poll_write_cb_is_io_handle = 0;
1096 1           SvREFCNT_dec(tmp);
1097             }
1098             #ifdef FEERSUM_HAS_H2
1099             if (unlikely(c->is_h2_stream)) {
1100             if (c->responding < RESPOND_SHUTDOWN) {
1101             feersum_h2_close_write(aTHX_ c);
1102             change_responding_state(c, RESPOND_SHUTDOWN);
1103             }
1104             } else
1105             #endif
1106 54 50         if (c->responding < RESPOND_SHUTDOWN) {
1107 54           finish_wbuf(c); // only adds terminator if use_chunked is set
1108 54           change_responding_state(c, RESPOND_SHUTDOWN);
1109 54           conn_write_ready(c);
1110             }
1111 54           RETVAL = 1;
1112             }
1113             else {
1114             trace("close reader fd=%d, c=%p\n", c->fd, c);
1115 7 50         if (c->poll_read_cb) {
1116 0           SV *tmp = c->poll_read_cb;
1117 0           c->poll_read_cb = NULL;
1118 0           SvREFCNT_dec(tmp);
1119             }
1120 7 100         if (c->rbuf) {
1121 1           SvREFCNT_dec(c->rbuf);
1122 1           c->rbuf = NULL;
1123             }
1124 7 50         if (c->fd >= 0
1125             #ifdef FEERSUM_HAS_H2
1126             && !c->is_h2_stream
1127             #endif
1128             )
1129 7           RETVAL = shutdown(c->fd, SHUT_RD);
1130             else
1131 0           RETVAL = -1; // already closed or H2 stream
1132 7           change_receiving_state(c, RECEIVE_SHUTDOWN);
1133             }
1134              
1135             // disassociate the handle from the conn
1136 61           SvREFCNT_dec(c->self);
1137 61           return RETVAL;
1138             }
1139              
1140             static SV*
1141 6           feersum_conn_guard(pTHX_ struct feer_conn *c, SV *guard)
1142             {
1143 6 100         if (guard) {
1144 4           SvREFCNT_dec(c->ext_guard); /* NULL-safe */
1145 4 50         c->ext_guard = SvOK(guard) ? newSVsv(guard) : NULL;
1146             }
1147 6 50         return c->ext_guard ? newSVsv(c->ext_guard) : &PL_sv_undef;
1148             }
1149              
1150             static void
1151 548           call_request_callback (struct feer_conn *c)
1152             {
1153             dTHX;
1154 548           dSP;
1155             int flags;
1156 548           struct feer_server *server = c->server;
1157 548           c->in_callback++;
1158 548           SvREFCNT_inc_void_NN(c->self);
1159 548           server->total_requests++;
1160              
1161             trace("request callback c=%p\n", c);
1162              
1163 548           ENTER;
1164 548           SAVETMPS;
1165 548 50         PUSHMARK(SP);
1166              
1167 548 100         if (server->request_cb_is_psgi) {
1168 93           HV *env = feersum_env(aTHX_ c);
1169 93 50         mXPUSHs(newRV_noinc((SV*)env));
1170 93           flags = G_EVAL|G_SCALAR;
1171             }
1172             else {
1173 455 50         mXPUSHs(feer_conn_2sv(c));
1174 455           flags = G_DISCARD|G_EVAL|G_VOID;
1175             }
1176              
1177 548           PUTBACK;
1178 548           int returned = call_sv(server->request_cb_cv, flags);
1179 548           SPAGAIN;
1180              
1181             trace("called request callback, errsv? %d\n", SvTRUE(ERRSV) ? 1 : 0);
1182              
1183 548 50         if (unlikely(SvTRUE(ERRSV))) {
    100          
1184 12           call_died(aTHX_ c, "request");
1185 12           returned = 0; // pretend nothing got returned
1186             }
1187              
1188 548           SV *psgi_response = NULL;
1189 548 100         if (server->request_cb_is_psgi && likely(returned >= 1)) {
    50          
1190 93           psgi_response = POPs;
1191 93           SvREFCNT_inc_void_NN(psgi_response);
1192             }
1193              
1194             trace("leaving request callback\n");
1195 548           PUTBACK;
1196              
1197 548 100         if (psgi_response) {
1198 93           feersum_handle_psgi_response(aTHX_ c, psgi_response, 1); // can_recurse
1199 93           SvREFCNT_dec(psgi_response);
1200             }
1201              
1202 548           c->in_callback--;
1203 548           SvREFCNT_dec(c->self);
1204              
1205 548 50         FREETMPS;
1206 548           LEAVE;
1207 548           }
1208              
1209             static void
1210 26           call_poll_callback (struct feer_conn *c, bool is_write)
1211             {
1212             dTHX;
1213 26           dSP;
1214              
1215 26 100         SV *cb = (is_write) ? c->poll_write_cb : c->poll_read_cb;
1216              
1217 26 50         if (unlikely(cb == NULL)) return;
1218              
1219 26           c->in_callback++;
1220              
1221             trace("%s poll callback c=%p cbrv=%p\n",
1222             is_write ? "write" : "read", c, cb);
1223              
1224 26           ENTER;
1225 26           SAVETMPS;
1226 26 50         PUSHMARK(SP);
1227 26           SV *hdl = new_feer_conn_handle(aTHX_ c, is_write);
1228 26 50         mXPUSHs(hdl);
1229 26           PUTBACK;
1230 26           call_sv(cb, G_DISCARD|G_EVAL|G_VOID);
1231 26           SPAGAIN;
1232              
1233             trace("called %s poll callback, errsv? %d\n",
1234             is_write ? "write" : "read", SvTRUE(ERRSV) ? 1 : 0);
1235              
1236 26 50         if (unlikely(SvTRUE(ERRSV))) {
    50          
1237 0 0         call_died(aTHX_ c, is_write ? "write poll" : "read poll");
1238             }
1239              
1240             // Neutralize the mortal handle before FREETMPS so its DESTROY doesn't
1241             // call feersum_close_handle and prematurely close the connection.
1242             // If the callback already called close(), SvUVX is already 0.
1243             {
1244 26           SV *inner = SvRV(hdl);
1245 26 50         if (SvUVX(inner)) {
1246 26           SvUVX(inner) = 0;
1247 26           SvREFCNT_dec(c->self); // balance new_feer_conn_handle's inc
1248             }
1249             }
1250              
1251             trace("leaving %s poll callback\n", is_write ? "write" : "read");
1252 26           PUTBACK;
1253 26 50         FREETMPS;
1254 26           LEAVE;
1255              
1256 26           c->in_callback--;
1257             }
1258              
1259             static void
1260 11           pump_io_handle (struct feer_conn *c)
1261             {
1262             dTHX;
1263 11           dSP;
1264              
1265 11 50         if (unlikely(c->poll_write_cb == NULL)) return;
1266              
1267 11           c->in_callback++;
1268              
1269             trace("pump io handle %d\n", c->fd);
1270              
1271 11           SV *old_rs = PL_rs;
1272 11 50         SvREFCNT_inc_simple_void(old_rs);
1273 11           PL_rs = sv_2mortal(newRV_noinc(newSViv(IO_PUMP_BUFSZ)));
1274 11           sv_setsv(get_sv("/", GV_ADD), PL_rs);
1275              
1276 11           ENTER;
1277 11           SAVETMPS;
1278              
1279 11 50         PUSHMARK(SP);
1280 11 50         XPUSHs(c->poll_write_cb);
1281 11           PUTBACK;
1282 11           int returned = call_method("getline", G_SCALAR|G_EVAL);
1283 11           SPAGAIN;
1284              
1285             trace("called getline on io handle fd=%d errsv=%d returned=%d\n",
1286             c->fd, SvTRUE(ERRSV) ? 1 : 0, returned);
1287              
1288 11 50         if (unlikely(SvTRUE(ERRSV))) {
    50          
1289             /* Restore PL_rs before call_died — it invokes Feersum::DIED which
1290             * is Perl code that (or whose callees) may read $/ */
1291 0           PL_rs = old_rs;
1292 0           sv_setsv(get_sv("/", GV_ADD), old_rs);
1293 0           call_died(aTHX_ c, "getline on io handle");
1294             // Clear poll callback to prevent re-invocation after error
1295 0 0         if (c->poll_write_cb) {
1296 0           SvREFCNT_dec(c->poll_write_cb);
1297 0           c->poll_write_cb = NULL;
1298             }
1299 0           c->poll_write_cb_is_io_handle = 0;
1300 0           goto done_pump_io;
1301             }
1302              
1303 11           SV *ret = NULL;
1304 11 50         if (returned > 0)
1305 11           ret = POPs;
1306 11 50         if (ret && SvMAGICAL(ret))
    50          
1307 0           ret = sv_2mortal(newSVsv(ret));
1308              
1309 11 50         if (unlikely(!ret || !SvOK(ret))) {
    100          
1310             // returned undef, so call the close method out of nicety
1311 5 50         PUSHMARK(SP);
1312 5 50         XPUSHs(c->poll_write_cb);
1313 5           PUTBACK;
1314 5           call_method("close", G_VOID|G_DISCARD|G_EVAL);
1315 5           SPAGAIN;
1316              
1317 5 50         if (unlikely(SvTRUE(ERRSV))) {
    50          
1318 0 0         trouble("Couldn't close body IO handle: %-p",ERRSV);
1319 0 0         sv_setsv(ERRSV, &PL_sv_undef);
1320             }
1321              
1322 5           SvREFCNT_dec(c->poll_write_cb);
1323 5           c->poll_write_cb = NULL;
1324 5           c->poll_write_cb_is_io_handle = 0;
1325 5           finish_wbuf(c);
1326 5           change_responding_state(c, RESPOND_SHUTDOWN);
1327              
1328 5           goto done_pump_io;
1329             }
1330              
1331 6 50         if (c->use_chunked)
1332 6           add_chunk_sv_to_wbuf(c, ret);
1333             else
1334 0           add_sv_to_wbuf(c, ret);
1335              
1336 11           done_pump_io:
1337             trace("leaving pump io handle %d\n", c->fd);
1338              
1339             // Restore PL_rs before FREETMPS — the mortal RV set at line 1245
1340             // would be freed by FREETMPS, leaving PL_rs as a dangling pointer.
1341 11           PL_rs = old_rs;
1342 11           sv_setsv(get_sv("/", GV_ADD), old_rs);
1343 11           SvREFCNT_dec(old_rs);
1344              
1345 11           PUTBACK;
1346 11 50         FREETMPS;
1347 11           LEAVE;
1348              
1349 11           c->in_callback--;
1350             }
1351              
1352             static int
1353 9           psgix_io_svt_get (pTHX_ SV *sv, MAGIC *mg)
1354             {
1355 9           dSP;
1356              
1357 9           struct feer_conn *c = sv_2feer_conn(mg->mg_obj);
1358             trace("invoking psgix.io magic for fd=%d\n", c->fd);
1359              
1360 9           sv_unmagic(sv, PERL_MAGIC_ext);
1361              
1362             #ifdef FEERSUM_HAS_H2
1363             /* H2 tunnel: create socketpair and expose sv[1] as the IO handle.
1364             * For PSGI: auto-send 200 HEADERS and enable response swallowing so
1365             * apps can use the same psgix.io code for H1 and H2 transparently. */
1366             if (c->is_h2_stream) {
1367             struct feer_h2_stream *stream = H2_STREAM_FROM_PC(c);
1368             if (!stream || !stream->is_tunnel) {
1369             trouble("psgix.io: not supported on regular H2 streams fd=%d\n", c->fd);
1370             return 0;
1371             }
1372             h2_tunnel_auto_accept(aTHX_ c, stream);
1373             /* Re-fetch stream: auto_accept → session_send may have freed it */
1374             stream = H2_STREAM_FROM_PC(c);
1375             if (!stream) {
1376             trouble("psgix.io: stream freed during auto_accept fd=%d\n", c->fd);
1377             return 0;
1378             }
1379             feer_h2_setup_tunnel(aTHX_ stream);
1380             if (!stream->tunnel_established) {
1381             trouble("psgix.io: tunnel setup failed fd=%d\n", c->fd);
1382             struct feer_conn *parent = stream->parent;
1383             SvREFCNT_inc_void_NN(parent->self);
1384             h2_submit_rst(parent->h2_session, stream->stream_id,
1385             NGHTTP2_INTERNAL_ERROR);
1386             feer_h2_session_send(parent);
1387             SvREFCNT_dec(parent->self);
1388             return 0;
1389             }
1390              
1391             ENTER;
1392             SAVETMPS;
1393             PUSHMARK(SP);
1394             XPUSHs(sv);
1395             mXPUSHs(newSViv(stream->tunnel_sv1));
1396             PUTBACK;
1397              
1398             call_pv("Feersum::Connection::_raw", G_VOID|G_DISCARD|G_EVAL);
1399             SPAGAIN;
1400              
1401             if (unlikely(SvTRUE(ERRSV))) {
1402             call_died(aTHX_ c, "psgix.io H2 tunnel magic");
1403             } else {
1404             SV *io_glob = SvRV(sv);
1405             GvSV(io_glob) = newRV_inc(c->self);
1406             stream->tunnel_sv1 = -1;
1407             }
1408              
1409             c->io_taken = 1;
1410             PUTBACK;
1411             FREETMPS;
1412             LEAVE;
1413             return 0;
1414             }
1415             #endif
1416              
1417             #ifdef FEERSUM_HAS_TLS
1418             /* TLS tunnel: create socketpair relay for psgix.io over TLS */
1419 9 50         if (c->tls) {
1420 0           feer_tls_setup_tunnel(c);
1421 0 0         if (!c->tls_tunnel) {
1422 0           trouble("psgix.io: TLS tunnel setup failed fd=%d\n", c->fd);
1423 0           return 0;
1424             }
1425              
1426 0           ENTER;
1427 0           SAVETMPS;
1428 0 0         PUSHMARK(SP);
1429 0 0         XPUSHs(sv);
1430 0 0         mXPUSHs(newSViv(c->tls_tunnel_sv1));
1431 0           PUTBACK;
1432              
1433 0           call_pv("Feersum::Connection::_raw", G_VOID|G_DISCARD|G_EVAL);
1434 0           SPAGAIN;
1435              
1436 0 0         if (unlikely(SvTRUE(ERRSV))) {
    0          
1437 0           call_died(aTHX_ c, "psgix.io TLS tunnel magic");
1438             /* fd NOT consumed by _raw on failure; conn cleanup will close it */
1439             } else {
1440 0           SV *io_glob = SvRV(sv);
1441 0           GvSV(io_glob) = newRV_inc(c->self);
1442 0           c->tls_tunnel_sv1 = -1;
1443             }
1444              
1445 0           c->io_taken = 1;
1446 0           stop_read_timer(c);
1447 0           stop_write_timer(c);
1448 0           PUTBACK;
1449 0 0         FREETMPS;
1450 0           LEAVE;
1451 0           return 0;
1452             }
1453             #endif
1454              
1455 9           ENTER;
1456 9           SAVETMPS;
1457              
1458 9 50         PUSHMARK(SP);
1459 9 50         XPUSHs(sv);
1460 9 50         mXPUSHs(newSViv(c->fd));
1461 9           PUTBACK;
1462              
1463 9           call_pv("Feersum::Connection::_raw", G_VOID|G_DISCARD|G_EVAL);
1464 9           SPAGAIN;
1465              
1466 9 50         if (unlikely(SvTRUE(ERRSV))) {
    50          
1467 0           call_died(aTHX_ c, "psgix.io magic");
1468             }
1469             else {
1470 9           SV *io_glob = SvRV(sv);
1471 9           GvSV(io_glob) = newRV_inc(c->self);
1472              
1473             // Put whatever remainder data into the socket buffer.
1474             // Optimizes for the websocket case.
1475             // Use return_from_psgix_io() to pull data back for keepalive.
1476 9 50         if (likely(c->rbuf && SvOK(c->rbuf) && SvCUR(c->rbuf))) {
    50          
    50          
    50          
1477             STRLEN rbuf_len;
1478 9           const char *rbuf_ptr = SvPV(c->rbuf, rbuf_len);
1479 9           IO *io = GvIOp(io_glob);
1480 9 50         if (unlikely(!io)) {
1481 0           trouble("psgix.io: GvIOp returned NULL fd=%d\n", c->fd);
1482             // Skip unread, data will remain in rbuf
1483             }
1484             else {
1485             // PerlIO_unread copies the data internally, so it's safe to
1486             // clear rbuf after. Use SvCUR_set to keep buffer allocated
1487             // (more efficient for potential reuse).
1488 9           SSize_t pushed = PerlIO_unread(IoIFP(io), (const void *)rbuf_ptr, rbuf_len);
1489 9 50         if (likely(pushed == (SSize_t)rbuf_len)) {
1490 9           SvCUR_set(c->rbuf, 0);
1491 0 0         } else if (pushed > 0) {
1492 0           sv_chop(c->rbuf, rbuf_ptr + pushed);
1493 0           trouble("PerlIO_unread partial: %zd of %"Sz_uf" bytes fd=%d\n",
1494             (size_t)pushed, (Sz)rbuf_len, c->fd);
1495             } else {
1496 0           trouble("PerlIO_unread failed in psgix.io magic fd=%d\n", c->fd);
1497             }
1498             }
1499             }
1500              
1501             // Stop Feersum's watchers - user now owns the socket
1502 9           stop_read_watcher(c);
1503 9           stop_read_timer(c);
1504 9           stop_write_timer(c);
1505             // don't stop write watcher in case there's outstanding data
1506              
1507 9           c->io_taken = 1;
1508             }
1509              
1510 9 50         FREETMPS;
1511 9           LEAVE;
1512              
1513 9           return 0;
1514             }
1515              
1516             #ifdef FEERSUM_HAS_H2
1517             static void
1518             h2_tunnel_auto_accept(pTHX_ struct feer_conn *c, struct feer_h2_stream *stream)
1519             {
1520             if (c->responding != RESPOND_NOT_STARTED)
1521             return;
1522             AV *empty_hdr = newAV();
1523             SV *status_sv = newSVpvs("200");
1524             feersum_start_response(aTHX_ c, status_sv, empty_hdr, 1);
1525             SvREFCNT_dec(status_sv);
1526             SvREFCNT_dec((SV *)empty_hdr);
1527             /* Re-fetch stream: feersum_start_response → feer_h2_session_send may
1528             * have freed it via h2_on_stream_close_cb (e.g. RST_STREAM queued) */
1529             stream = H2_STREAM_FROM_PC(c);
1530             if (stream)
1531             stream->tunnel_swallow_response = 1;
1532             }
1533              
1534             static void
1535             pump_h2_io_handle(pTHX_ struct feer_conn *c, SV *body)
1536             {
1537             dSP;
1538             SV *io = newSVsv(body);
1539             c->in_callback++;
1540             SV *old_rs = PL_rs;
1541             SvREFCNT_inc_simple_void(old_rs);
1542             PL_rs = sv_2mortal(newRV_noinc(newSViv(IO_PUMP_BUFSZ)));
1543             sv_setsv(get_sv("/", GV_ADD), PL_rs);
1544             ENTER;
1545             SAVETMPS;
1546             for (;;) {
1547             ENTER; SAVETMPS;
1548             PUSHMARK(SP);
1549             XPUSHs(io);
1550             PUTBACK;
1551             int returned = call_method("getline", G_SCALAR|G_EVAL);
1552             SPAGAIN;
1553             if (unlikely(SvTRUE(ERRSV))) {
1554             /* Restore PL_rs before call_died — it invokes Feersum::DIED which
1555             * is Perl code that (or whose callees) may read $/ */
1556             PL_rs = old_rs;
1557             sv_setsv(get_sv("/", GV_ADD), old_rs);
1558             call_died(aTHX_ c, "getline on H2 io handle");
1559             PUTBACK; FREETMPS; LEAVE;
1560             break;
1561             }
1562             SV *ret = NULL;
1563             if (returned > 0) ret = POPs;
1564             if (ret && SvMAGICAL(ret)) ret = sv_2mortal(newSVsv(ret));
1565             if (!ret || !SvOK(ret)) {
1566             PUSHMARK(SP); XPUSHs(io); PUTBACK;
1567             call_method("close", G_VOID|G_DISCARD|G_EVAL);
1568             SPAGAIN;
1569             if (unlikely(SvTRUE(ERRSV))) {
1570             trouble("Couldn't close body IO handle: %-p",ERRSV);
1571             sv_setsv(ERRSV, &PL_sv_undef);
1572             }
1573             PUTBACK; FREETMPS; LEAVE;
1574             feersum_h2_close_write(aTHX_ c);
1575             break;
1576             }
1577             feersum_h2_write_chunk(aTHX_ c, ret);
1578             PUTBACK; FREETMPS; LEAVE;
1579             }
1580             FREETMPS; LEAVE;
1581             PL_rs = old_rs;
1582             sv_setsv(get_sv("/", GV_ADD), old_rs);
1583             SvREFCNT_dec(old_rs);
1584             c->in_callback--;
1585             SvREFCNT_dec(io);
1586             }
1587              
1588             static SV*
1589             feersum_env_method_h2(pTHX_ struct feer_conn *c, struct feer_req *r)
1590             {
1591             if (unlikely(c->is_h2_stream)) {
1592             struct feer_h2_stream *stream = H2_STREAM_FROM_PC(c);
1593             if (stream && stream->is_tunnel && stream->h2_protocol)
1594             return SvREFCNT_inc_simple_NN(method_GET);
1595             }
1596             return feersum_env_method(aTHX_ r);
1597             }
1598             #endif