# -*- coding: utf-8 -*-
"""HTTP - Hypertext Transfer Protocol
========================================
.. module:: pcapkit.protocols.application.http
:mod:`pcapkit.protocols.application.http` contains
:class:`~pcapkit.protocols.application.http.HTTP`
only, which is a base class for Hypertext Transfer
Protocol (HTTP) [*]_ family, eg.
:class:`HTTP/1.* <pcapkit.protocols.application.application.httpv1>`
and :class:`HTTP/2 <pcapkit.protocols.application.application.httpv2>`.
.. [*] https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
"""
import contextlib
from typing import TYPE_CHECKING, Generic
from pcapkit.protocols.application.application import Application
from pcapkit.protocols.protocol import _PT, _ST
from pcapkit.utilities.exceptions import ProtocolError
if TYPE_CHECKING:
from typing import Any, Optional
from typing_extensions import Literal
__all__ = ['HTTP']
[docs]
class HTTP(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`]
"""
if TYPE_CHECKING:
#: Saved subclass protocol data (only for HTTP base class).
_http: 'HTTP[_PT, _ST]'
##########################################################################
# Properties.
##########################################################################
@property
def name(self) -> 'Literal["Hypertext Transfer Protocol"]':
"""Name of current protocol."""
return 'Hypertext Transfer Protocol'
@property
def alias(self) -> 'Literal["HTTP/0.9", "HTTP/1.0", "HTTP/1.1", "HTTP/2"]':
"""Acronym of current protocol."""
return f'HTTP/{self.version}' # type: ignore[return-value]
@property
def length(self) -> 'int':
"""Header length of current protocol."""
return self._length
@property
def version(self) -> 'Literal["0.9", "1.0", "1.1", "2"]':
"""Version of current protocol."""
return self._version
##########################################################################
# Methods.
##########################################################################
[docs]
@classmethod
def id(cls) -> 'tuple[Literal["HTTP"], Literal["HTTPv1"], Literal["HTTPv2"]]':
"""Index ID of the protocol."""
return ('HTTP', 'HTTPv1', 'HTTPv2')
[docs]
def read(self, length: 'Optional[int]' = None, *,
version: 'Optional[Literal[1, 2]]' = None, **kwargs: 'Any') -> '_PT':
"""Read (parse) packet data.
Args:
length: Length of packet data.
version: Version of HTTP.
**kwargs: Arbitrary keyword arguments.
Returns:
Parsed packet data.
"""
if length is None:
length = len(self)
if version is None:
http = self._guess_version(length, **kwargs)
else:
if version == 1:
from pcapkit.protocols.application.httpv1 import HTTP as protocol # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
elif version == 2:
from pcapkit.protocols.application.httpv2 import HTTP as protocol # type: ignore[assignment] # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
else:
raise ProtocolError(f"invalid HTTP version: {version}")
http = protocol(self._file, length, **kwargs)
self._version = http.version
self._length = http.length
self._http = http
return http.info
[docs]
def make(self,
version: 'Literal[1, 2]' = 1,
**kwargs: 'Any') -> '_ST':
"""Make (construct) packet data.
Args:
version: Version of HTTP.
**kwargs: Arbitrary keyword arguments.
Returns:
bytes: Constructed packet data.
"""
if version == 1:
from pcapkit.protocols.application.httpv1 import HTTP as protocol # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
elif version == 2:
from pcapkit.protocols.application.httpv2 import HTTP as protocol # type: ignore[assignment] # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
else:
raise ProtocolError(f"invalid HTTP version: {version}")
return protocol.make(**kwargs) # type: ignore[return-value]
##########################################################################
# Utilities.
##########################################################################
[docs]
@classmethod
def _make_data(cls, data: '_PT') -> 'dict[str, Any]': # type: ignore[override]
"""Create key-value pairs from ``data`` for protocol construction.
Args:
data: protocol data
Returns:
Key-value pairs for protocol construction.
"""
version = data.get('version', 0)
if version == 1:
from pcapkit.protocols.application.httpv1 import HTTP as protocol
elif version == 2:
from pcapkit.protocols.application.httpv2 import HTTP as protocol # type: ignore[assignment] # isort: skip
else:
raise ProtocolError(f"invalid HTTP version: {version}")
return protocol._make_data(data) # type: ignore[arg-type]
[docs]
def _guess_version(self, length: 'int', **kwargs: 'Any') -> 'HTTP':
"""Guess HTTP version.
Args:
length: Length of packet data.
Keyword Args:
**kwargs: Arbitrary keyword arguments.
Returns:
Parsed packet data.
"""
from pcapkit.protocols.application.httpv1 import HTTP as HTTPv1 # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
with contextlib.suppress(ProtocolError):
return HTTPv1(self._data, length, **kwargs)
from pcapkit.protocols.application.httpv2 import HTTP as HTTPv2 # isort: skip # pylint: disable=line-too-long,import-outside-toplevel
with contextlib.suppress(ProtocolError):
return HTTPv2(self._data, length, **kwargs)
raise ProtocolError("unknown HTTP version")