Skip to content

http: _guess_version's HTTP/2 arm is unreachable, and PayloadField.protocol does no case folding #787

Description

@JarryShaw

Found while investigating #682, and independent of how that issue is decided. Two separate defects in the same neighbourhood; both measured.

1. _guess_version's HTTP/2 arm is unreachable

pcapkit/protocols/application/http.py:202-207 tries HTTP/1 then HTTP/2, each under contextlib.suppress(ProtocolError):

with contextlib.suppress(ProtocolError):
    return HTTPv1(self._data, length, **kwargs)
with contextlib.suppress(ProtocolError):
    return HTTPv2(self._data, length, **kwargs)

But httpv1.HTTP raises a bare ValueError on HTTP/2 wire bytes, not a ProtocolError. ValueError is not suppressed, so it propagates out of the first arm and the HTTP/2 arm is never reached.

Measured on b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' plus a SETTINGS frame: httpv2.HTTP parses it and reports version='2', while the proxy raises ValueError: not enough values to unpack (expected 2, got 1).

The asymmetry is visible two screens up: the explicit version= path at :115-120 does wrap it —

except ProtocolError:
    raise
except ValueError as error:
    raise ProtocolError(f'HTTP/{version}: invalid format') from error

— so read(version=2) works and read() cannot. Combined with the port bindings (tcp.py:330-331 binds 80/8080 to concrete HTTP/1, and httpv2.HTTP is the value of no port on any transport), HTTP/2 is reachable only by explicit version=2 or direct instantiation, never automatically. That undercuts the proxy's purpose as a dispatcher.

Either suppress ValueError alongside ProtocolError in _guess_version, or make httpv1.HTTP raise a ProtocolError for a malformed request line the way the explicit path already normalises it to. The second is the better fix if ValueError from that constructor is never meaningful to a caller — worth checking before choosing.

2. PayloadField.protocol does no case folding

pcapkit/corekit/fields/misc.py:265-268:

if isinstance(protocol, str):
    from pcapkit.protocols import __proto__
    protocol = cast('Type[_TP]', __proto__.get(protocol))

__proto__ is keyed on protocol.__name__.upper() (pcapkit/foundation/registry/protocols.py:219), but this reader does not .upper() its argument and uses .get, so a lowercase or mixed-case name silently resolves to None. PayloadField(protocol='http') therefore yields Raw rather than HTTP, with no warning — today, independent of any registry displacement.

Every other reader compares against id() as well and so tolerates the miss; this one does not. One line plus a test.

Both are separable from #682's design question and from each other.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugfixPull requests that fix a defect (fix: subject prefix)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions