Skip to content

H2 fixes#3616

Closed
saghul wants to merge 3 commits into
warmcat:mainfrom
saghul:fix-h2-server-post-deferred-completion
Closed

H2 fixes#3616
saghul wants to merge 3 commits into
warmcat:mainfrom
saghul:fix-h2-server-post-deferred-completion

Conversation

@saghul

@saghul saghul commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
  • h2: server: decrement rx_content_remain when draining deferred POST body
  • h2: capture unknown (custom) headers into the UHO store
  • h2: server: complete empty request body signalled by END_STREAM on HEADERS

saghul added 3 commits June 19, 2026 11:01
lws_h2_bind_for_post_before_action() delivers any request-body bytes that
were stashed on the stream buflist while the stream was in
LRS_DEFERRING_ACTION, decrementing rx_content_length as it goes.  But the
inline DATA path in lws_read_h1() and the HTTP_BODY_COMPLETION decision
both track rx_content_remain (initialized to the full content-length),
not rx_content_length.

When a request body is split across the DEFERRING_ACTION boundary -- part
stashed and dumped here, the rest arriving later on the inline path --
rx_content_remain is never reduced for the stashed part, so it can never
reach 0.  HTTP_BODY_COMPLETION therefore never fires and the request
hangs until it times out.  Bodies small enough to be fully stashed
complete via the explicit callback at the end of this function, which is
why only larger/split POST bodies were affected.

Keep rx_content_remain in step with rx_content_length as the stashed body
is delivered.
The lws_hdr_custom_*() accessors read unknown headers out of the ah's
UHO linked list, but that list was only ever populated by the h1 text
header parser; for h2 the accessors early-returned on mux_substream and
custom headers were invisible (commit da8995b disabled the speculative
h1 path for substreams).

The h1 path can't be reused as-is for h2: the header name arrives through
hpack one character at a time (fed to lws_parse purely for known-header
recognition) and the value never passes through the h1 unknown-value
collector at all, so neither the name nor the value were being stored.

Build the UHO entry directly in the hpack decoder instead, where both the
name and value bytes are available:

 - reserve the UHO entry header before the name (speculatively, per the
   h1 approach),
 - collect the name as it decodes, finalizing it (with the trailing ':'
   the accessors expect) and linking it into unk_ll when lws does not
   recognize the header, or dropping it if it does,
 - collect the value bytes that the IGNORE-entry path otherwise discards,
 - record the value length when the value completes.

To keep a single writer of ah->pos during substream name parsing, the h1
text parser no longer also lays the name bytes down for mux substreams.

Relax the mux_substream guard on the three lws_hdr_custom_*() accessors
so the now-populated list is readable for h2.
…ADERS

A client sends "no request body" over HTTP/2 by setting END_STREAM on the
HEADERS frame and omitting Content-Length (e.g. an empty POST). lws_http_action()
re-derives the body expectation from the Content-Length header and the method:
for a body-bearing method with no Content-Length it defaults rx_content_length to
100MB and waits for DATA frames that, after END_STREAM, will never arrive -- so
the request stalls until it times out. (An explicit "Content-Length: 0" works,
because that takes the explicit-zero path.)

For a mux (h2) substream whose END_STREAM already arrived with the HEADERS and
that carried no Content-Length, treat the body as an explicit zero-length body,
exactly as "Content-Length: 0" would. h3 already does the equivalent in ops-h3.c.

Fixes empty-body POST/PUT/PATCH over h2 hanging server-side (reproduces with
`curl --http2 -X POST` against any lws h2 server).
@saghul saghul force-pushed the fix-h2-server-post-deferred-completion branch from 6199df5 to bf64032 Compare June 19, 2026 09:01
@lws-team

Copy link
Copy Markdown
Member

Yeah these are great, thanks, they're pushed on main

@lws-team lws-team closed this Jun 19, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants