HTTP - Hypertext Transfer Protocol

pcapkit.protocols.application.http contains HTTP, which is a base class for Hypertext Transfer Protocol (HTTP) [*] family, eg. HTTP/1.* and HTTP/2, and test_start_line(), which tells whether a payload opens with an HTTP/1.* start line.

class pcapkit.protocols.application.http.HTTP(file=None, length=None, **kwargs)[source]

Bases: Application[_PT, _ST], Generic[_PT, _ST]

This class implements all protocols in HTTP family.

  • Hypertext Transfer Protocol (HTTP/1.1) [RFC 7230]

  • Hypertext Transfer Protocol version 2 (HTTP/2) [RFC 7540]

property name: Literal['Hypertext Transfer Protocol']

Name of current protocol.

property alias: Literal['HTTP/0.9', 'HTTP/1.0', 'HTTP/1.1', 'HTTP/2']

Acronym of current protocol.

property length: int

Header length of current protocol.

property version: Literal['0.9', '1.0', '1.1', '2']

Version of current protocol.

classmethod id()[source]

Index ID of the protocol.

Return type:

tuple[Literal['HTTP'], Literal['HTTPv1'], Literal['HTTPv2']]

read(length=None, *, version=None, preface=False, **kwargs)[source]

Read (parse) packet data.

Parameters:
  • length (int | None) – Length of packet data.

  • version (Literal[1, 2] | None) – Version of HTTP.

  • preface (bool) – Construction keyword of make(); whether a preface is parsed is decided by the octets alone.

  • **kwargs (Any) – Arbitrary keyword arguments.

Return type:

TypeVar(_PT, bound= Data)

Returns:

Parsed packet data. When the payload opens with the HTTP/2 connection preface, its 24 octets are recorded as preface.

make(version=1, *, preface=False, **kwargs)[source]

Make (construct) packet data.

Parameters:
  • version (Literal[1, 2]) – Version of HTTP.

  • preface (bool) – Whether the HTTP/2 connection preface goes ahead of the frame; pack() writes it.

  • **kwargs (Any) – Arbitrary keyword arguments.

Return type:

TypeVar(_ST, bound= Schema)

Returns:

The header schema the versioned make constructs.

Raises:

ProtocolError – If version is unknown, or if preface is requested for a version other than HTTP/2.

classmethod _make_data(data)[source]

Create key-value pairs from data for protocol construction.

Parameters:

data (TypeVar(_PT, bound= Data)) – protocol data

Return type:

dict[str, Any]

Returns:

Key-value pairs for protocol construction, including the version that make() dispatches on.

Raises:

ProtocolError – If data is neither HTTP/1.* nor HTTP/2 data.

_guess_version(length, **kwargs)[source]

Identify the HTTP version of the payload, and parse it with that version.

The payload is identified first and trial-parsed only as a last resort. Trying each version in turn and taking whichever parser does not object answers “did a parser accept this?” where the question is “what is this?”: the HTTP/2 preface reads as a frame whose declared length is 5,265,993 (b'PRI' as a 24-bit integer), and text that is not HTTP at all can be accepted the same way.

Parameters:

length (int) – Length of packet data.

Keyword Arguments:

**kwargs – Arbitrary keyword arguments.

Return type:

HTTP

Returns:

Parsed packet data.

Raises:

ProtocolError – If no version in the family accepts the payload, or if an identified version’s own parser refuses it. Only a ProtocolError from a trial parse falls through to the next version, and the final HTTP/2 trial also absorbs struct.error; any other exception from either HTTP/1 arm, or from that HTTP/2 trial, propagates unconverted.

Note

Two things this deliberately does not decide, because one payload with no flow context cannot:

  • An Upgrade: h2c exchange (RFC 7540 Section 3.2, deprecated but not removed by RFC 9113 Section 3.1) stays HTTP/1.1 here, and is correctly HTTP/1.1: on the wire the upgrade request and its 101 Switching Protocols response are HTTP/1.1 messages and parse as such. The switch takes effect only after the 101, so deciding that later segments of the same connection are HTTP/2 needs per-connection state keyed on the 4-tuple, and this method is handed a single payload with no such context. Recognising the Upgrade: h2c field is possible; acting on it is not, so it is left alone rather than half-implemented.

  • A mid-stream segment – a bare HTTP/2 frame header with no preface ahead of it, or an opaque HTTP/1 message body – is genuinely undecidable from one payload, and the honest answer is the Raw that an escaping ProtocolError becomes under beholder(). The nine-octet frame header is therefore never used to identify HTTP/2 ahead of HTTP/1: as identification it misfires on binary HTTP/1 bodies, which is precisely how garbage text was classified HTTP/2 to begin with. A bare frame is parsed as HTTP/2 only by the fall-through below, and only if its header is one a conforming sender could write (see _test_http2_frame()): a frame type RFC 9113 defines, no undefined flag, and a stream that type may be sent on. Nine zero octets – a DATA frame on stream 0, a protocol error under RFC 9113 Section 6.1 – are not guessed as HTTP/2.

_preface_length = 0

Octets consumed ahead of the identified version’s own header – in practice the 24-octet HTTP/2 connection preface, when _guess_version() identified one and parsed the frame that follows it. They belong to this packet’s header rather than to its payload, so read() adds them to length; otherwise ProtocolBase.__init__’s self._info.__update__(packet=self.packet.payload) would slice the payload from octet 9 of a buffer whose frame starts at octet 24 and report the tail of the preface as packet payload.

pcapkit.protocols.application.http.test_start_line(data)[source]

Whether data opens with an HTTP/1.* start line.

This is a classification predicate and parses nothing: it answers “is this HTTP/1?” for HTTP._guess_version. Classifying by trial-parsing every version in the family would decide a payload that is not HTTP at all by which parser happened to fail less loudly.

Parameters:

data (bytes) – Payload to classify.

Return type:

bool

Returns:

Whether the payload’s first line is a request-line or a status-line (RFC 9112 Section 2.1).

Note

The acceptance rule is deliberately the same one HTTP._read_http_header applies – _RE_METHOD with _RE_VERSION for a request, _RE_VERSION with _RE_STATUS for a response – which is why those three patterns are defined beside this predicate and httpv1 imports them from here. The two must accept the same start lines: a predicate looser than the parser classifies payloads the parser then refuses, and one tighter than the parser hands real HTTP/1 to a later arm. test_start_line_predicate_agrees_with_the_httpv1_parser pins that agreement.

The checks the parser performs before those patterns are mirrored too, and this is not pedantry – they are the whole reason the HTTP/2 connection preface is not claimed here. PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n is deliberately a well-formed HTTP/1.1 request line (RFC 9113 Section 3.4), so a predicate that tested only the first line would answer True for it. Split at the header/body separator first, as HTTP.read does, and the preface’s header is exactly PRI * HTTP/2.0 (_HTTP2_PREFACE_HEADER), which the parser refuses by name and so does this. Any other header with no CRLF in it is a start line with zero field lines, which RFC 9112 Section 2.1 allows and both accept (#1041).

An HTTP/0.9 request line carries only two tokens and so is not recognised here either, matching the parser, which raises on fewer than three.

pcapkit.protocols.application.http._HTTP2_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'

The HTTP/2 connection preface (RFC 9113 Section 3.4). A client opens every HTTP/2 connection – with prior knowledge, over TLS, or after an upgrade – by sending exactly these 24 octets, and the sequence is designed to be identifiable without parsing: it is a well-formed HTTP/1.1 request line whose method PRI is reserved and permanently unregistered, so no valid HTTP/1 message can begin with it and a prefix compare cannot false-positive on one. That makes it a positive identification rather than a heuristic, which is why HTTP._guess_version() tests it before attempting any parse.

Footnotes