File Coverage

feersum_psgi.c.inc
Criterion Covered Total %
statement 611 735 83.1
branch 441 718 61.4
condition n/a
subroutine n/a
pod n/a
total 1052 1453 72.4


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