1# -*- coding: utf-8 -*-
2"""
3h2/config
4~~~~~~~~~
5
6Objects for controlling the configuration of the HTTP/2 stack.
7"""
8
9
10class _BooleanConfigOption(object):
11    """
12    Descriptor for handling a boolean config option.  This will block
13    attempts to set boolean config options to non-bools.
14    """
15    def __init__(self, name):
16        self.name = name
17        self.attr_name = '_%s' % self.name
18
19    def __get__(self, instance, owner):
20        return getattr(instance, self.attr_name)
21
22    def __set__(self, instance, value):
23        if not isinstance(value, bool):
24            raise ValueError("%s must be a bool" % self.name)
25        setattr(instance, self.attr_name, value)
26
27
28class DummyLogger(object):
29    """
30    An Logger object that does not actual logging, hence a DummyLogger.
31
32    For the class the log operation is merely a no-op. The intent is to avoid
33    conditionals being sprinkled throughout the hyper-h2 code for calls to
34    logging functions when no logger is passed into the corresponding object.
35    """
36    def __init__(self, *vargs):
37        pass
38
39    def debug(self, *vargs, **kwargs):
40        """
41        No-op logging. Only level needed for now.
42        """
43        pass
44
45    def trace(self, *vargs, **kwargs):
46        """
47        No-op logging. Only level needed for now.
48        """
49        pass
50
51
52class H2Configuration(object):
53    """
54    An object that controls the way a single HTTP/2 connection behaves.
55
56    This object allows the users to customize behaviour. In particular, it
57    allows users to enable or disable optional features, or to otherwise handle
58    various unusual behaviours.
59
60    This object has very little behaviour of its own: it mostly just ensures
61    that configuration is self-consistent.
62
63    :param client_side: Whether this object is to be used on the client side of
64        a connection, or on the server side. Affects the logic used by the
65        state machine, the default settings values, the allowable stream IDs,
66        and several other properties. Defaults to ``True``.
67    :type client_side: ``bool``
68
69    :param header_encoding: Controls whether the headers emitted by this object
70        in events are transparently decoded to ``unicode`` strings, and what
71        encoding is used to do that decoding. This defaults to ``None``,
72        meaning that headers will be returned as bytes. To automatically
73        decode headers (that is, to return them as unicode strings), this can
74        be set to the string name of any encoding, e.g. ``'utf-8'``.
75
76        .. versionchanged:: 3.0.0
77           Changed default value from ``'utf-8'`` to ``None``
78
79    :type header_encoding: ``str``, ``False``, or ``None``
80
81    :param validate_outbound_headers: Controls whether the headers emitted
82        by this object are validated against the rules in RFC 7540.
83        Disabling this setting will cause outbound header validation to
84        be skipped, and allow the object to emit headers that may be illegal
85        according to RFC 7540. Defaults to ``True``.
86    :type validate_outbound_headers: ``bool``
87
88    :param normalize_outbound_headers: Controls whether the headers emitted
89        by this object are normalized before sending.  Disabling this setting
90        will cause outbound header normalization to be skipped, and allow
91        the object to emit headers that may be illegal according to
92        RFC 7540. Defaults to ``True``.
93    :type normalize_outbound_headers: ``bool``
94
95    :param validate_inbound_headers: Controls whether the headers received
96        by this object are validated against the rules in RFC 7540.
97        Disabling this setting will cause inbound header validation to
98        be skipped, and allow the object to receive headers that may be illegal
99        according to RFC 7540. Defaults to ``True``.
100    :type validate_inbound_headers: ``bool``
101
102    :param normalize_inbound_headers: Controls whether the headers received by
103        this object are normalized according to the rules of RFC 7540.
104        Disabling this setting may lead to hyper-h2 emitting header blocks that
105        some RFCs forbid, e.g. with multiple cookie fields.
106
107        .. versionadded:: 3.0.0
108
109    :type normalize_inbound_headers: ``bool``
110
111    :param logger: A logger that conforms to the requirements for this module,
112        those being no I/O and no context switches, which is needed in order
113        to run in asynchronous operation.
114
115        .. versionadded:: 2.6.0
116
117    :type logger: ``logging.Logger``
118    """
119    client_side = _BooleanConfigOption('client_side')
120    validate_outbound_headers = _BooleanConfigOption(
121        'validate_outbound_headers'
122    )
123    normalize_outbound_headers = _BooleanConfigOption(
124        'normalize_outbound_headers'
125    )
126    validate_inbound_headers = _BooleanConfigOption(
127        'validate_inbound_headers'
128    )
129    normalize_inbound_headers = _BooleanConfigOption(
130        'normalize_inbound_headers'
131    )
132
133    def __init__(self,
134                 client_side=True,
135                 header_encoding=None,
136                 validate_outbound_headers=True,
137                 normalize_outbound_headers=True,
138                 validate_inbound_headers=True,
139                 normalize_inbound_headers=True,
140                 logger=None):
141        self.client_side = client_side
142        self.header_encoding = header_encoding
143        self.validate_outbound_headers = validate_outbound_headers
144        self.normalize_outbound_headers = normalize_outbound_headers
145        self.validate_inbound_headers = validate_inbound_headers
146        self.normalize_inbound_headers = normalize_inbound_headers
147        self.logger = logger or DummyLogger(__name__)
148
149    @property
150    def header_encoding(self):
151        """
152        Controls whether the headers emitted by this object in events are
153        transparently decoded to ``unicode`` strings, and what encoding is used
154        to do that decoding. This defaults to ``None``, meaning that headers
155        will be returned as bytes. To automatically decode headers (that is, to
156        return them as unicode strings), this can be set to the string name of
157        any encoding, e.g. ``'utf-8'``.
158        """
159        return self._header_encoding
160
161    @header_encoding.setter
162    def header_encoding(self, value):
163        """
164        Enforces constraints on the value of header encoding.
165        """
166        if not isinstance(value, (bool, str, type(None))):
167            raise ValueError("header_encoding must be bool, string, or None")
168        if value is True:
169            raise ValueError("header_encoding cannot be True")
170        self._header_encoding = value
171