Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

128 linhas
4.9 KiB

  1. import sys
  2. import re
  3. __all__ = ["ProtocolError", "LocalProtocolError", "RemoteProtocolError",
  4. "validate", "make_sentinel", "bytesify"]
  5. class ProtocolError(Exception):
  6. """Exception indicating a violation of the HTTP/1.1 protocol.
  7. This as an abstract base class, with two concrete base classes:
  8. :exc:`LocalProtocolError`, which indicates that you tried to do something
  9. that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which
  10. indicates that the remote peer tried to do something that HTTP/1.1 says is
  11. illegal. See :ref:`error-handling` for details.
  12. In addition to the normal :exc:`Exception` features, it has one attribute:
  13. .. attribute:: error_status_hint
  14. This gives a suggestion as to what status code a server might use if
  15. this error occurred as part of a request.
  16. For a :exc:`RemoteProtocolError`, this is useful as a suggestion for
  17. how you might want to respond to a misbehaving peer, if you're
  18. implementing a server.
  19. For a :exc:`LocalProtocolError`, this can be taken as a suggestion for
  20. how your peer might have responded to *you* if h11 had allowed you to
  21. continue.
  22. The default is 400 Bad Request, a generic catch-all for protocol
  23. violations.
  24. """
  25. def __init__(self, msg, error_status_hint=400):
  26. if type(self) is ProtocolError:
  27. raise TypeError("tried to directly instantiate ProtocolError")
  28. Exception.__init__(self, msg)
  29. self.error_status_hint = error_status_hint
  30. # Strategy: there are a number of public APIs where a LocalProtocolError can
  31. # be raised (send(), all the different event constructors, ...), and only one
  32. # public API where RemoteProtocolError can be raised
  33. # (receive_data()). Therefore we always raise LocalProtocolError internally,
  34. # and then receive_data will translate this into a RemoteProtocolError.
  35. #
  36. # Internally:
  37. # LocalProtocolError is the generic "ProtocolError".
  38. # Externally:
  39. # LocalProtocolError is for local errors and RemoteProtocolError is for
  40. # remote errors.
  41. class LocalProtocolError(ProtocolError):
  42. def _reraise_as_remote_protocol_error(self):
  43. # After catching a LocalProtocolError, use this method to re-raise it
  44. # as a RemoteProtocolError. This method must be called from inside an
  45. # except: block.
  46. #
  47. # An easy way to get an equivalent RemoteProtocolError is just to
  48. # modify 'self' in place.
  49. self.__class__ = RemoteProtocolError
  50. # But the re-raising is somewhat non-trivial -- you might think that
  51. # now that we've modified the in-flight exception object, that just
  52. # doing 'raise' to re-raise it would be enough. But it turns out that
  53. # this doesn't work, because Python tracks the exception type
  54. # (exc_info[0]) separately from the exception object (exc_info[1]),
  55. # and we only modified the latter. So we really do need to re-raise
  56. # the new type explicitly.
  57. if sys.version_info[0] >= 3:
  58. # On py3, the traceback is part of the exception object, so our
  59. # in-place modification preserved it and we can just re-raise:
  60. raise self
  61. else:
  62. # On py2, preserving the traceback requires 3-argument
  63. # raise... but on py3 this is a syntax error, so we have to hide
  64. # it inside an exec
  65. exec("raise RemoteProtocolError, self, sys.exc_info()[2]")
  66. class RemoteProtocolError(ProtocolError):
  67. pass
  68. try:
  69. _fullmatch = type(re.compile('')).fullmatch
  70. except AttributeError:
  71. def _fullmatch(regex, data): # version specific: Python < 3.4
  72. match = regex.match(data)
  73. if match and match.end() != len(data):
  74. match = None
  75. return match
  76. def validate(regex, data, msg="malformed data", *format_args):
  77. match = _fullmatch(regex, data)
  78. if not match:
  79. if format_args:
  80. msg = msg.format(*format_args)
  81. raise LocalProtocolError(msg)
  82. return match.groupdict()
  83. # Sentinel values
  84. #
  85. # - Inherit identity-based comparison and hashing from object
  86. # - Have a nice repr
  87. # - Have a *bonus property*: type(sentinel) is sentinel
  88. #
  89. # The bonus property is useful if you want to take the return value from
  90. # next_event() and do some sort of dispatch based on type(event).
  91. class _SentinelBase(type):
  92. def __repr__(self):
  93. return self.__name__
  94. def make_sentinel(name):
  95. cls = _SentinelBase(name, (_SentinelBase,), {})
  96. cls.__class__ = cls
  97. return cls
  98. # Used for methods, request targets, HTTP versions, header names, and header
  99. # values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always
  100. # returns bytes.
  101. def bytesify(s):
  102. # Fast-path:
  103. if type(s) is bytes:
  104. return s
  105. if isinstance(s, str):
  106. s = s.encode("ascii")
  107. if isinstance(s, int):
  108. raise TypeError("expected bytes-like object, not int")
  109. return bytes(s)