You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
5.3 KiB

4 years ago
  1. # defusedxml
  2. #
  3. # Copyright (c) 2013 by Christian Heimes <christian@python.org>
  4. # Licensed to PSF under a Contributor Agreement.
  5. # See http://www.python.org/psf/license for licensing details.
  6. """Defused xmlrpclib
  7. Also defuses gzip bomb
  8. """
  9. from __future__ import print_function, absolute_import
  10. import io
  11. from .common import (
  12. DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden, PY3)
  13. if PY3:
  14. __origin__ = "xmlrpc.client"
  15. from xmlrpc.client import ExpatParser
  16. from xmlrpc import client as xmlrpc_client
  17. from xmlrpc import server as xmlrpc_server
  18. from xmlrpc.client import gzip_decode as _orig_gzip_decode
  19. from xmlrpc.client import GzipDecodedResponse as _OrigGzipDecodedResponse
  20. else:
  21. __origin__ = "xmlrpclib"
  22. from xmlrpclib import ExpatParser
  23. import xmlrpclib as xmlrpc_client
  24. xmlrpc_server = None
  25. from xmlrpclib import gzip_decode as _orig_gzip_decode
  26. from xmlrpclib import GzipDecodedResponse as _OrigGzipDecodedResponse
  27. try:
  28. import gzip
  29. except ImportError:
  30. gzip = None
  31. # Limit maximum request size to prevent resource exhaustion DoS
  32. # Also used to limit maximum amount of gzip decoded data in order to prevent
  33. # decompression bombs
  34. # A value of -1 or smaller disables the limit
  35. MAX_DATA = 30 * 1024 * 1024 # 30 MB
  36. def defused_gzip_decode(data, limit=None):
  37. """gzip encoded data -> unencoded data
  38. Decode data using the gzip content encoding as described in RFC 1952
  39. """
  40. if not gzip:
  41. raise NotImplementedError
  42. if limit is None:
  43. limit = MAX_DATA
  44. f = io.BytesIO(data)
  45. gzf = gzip.GzipFile(mode="rb", fileobj=f)
  46. try:
  47. if limit < 0: # no limit
  48. decoded = gzf.read()
  49. else:
  50. decoded = gzf.read(limit + 1)
  51. except IOError:
  52. raise ValueError("invalid data")
  53. f.close()
  54. gzf.close()
  55. if limit >= 0 and len(decoded) > limit:
  56. raise ValueError("max gzipped payload length exceeded")
  57. return decoded
  58. class DefusedGzipDecodedResponse(gzip.GzipFile if gzip else object):
  59. """a file-like object to decode a response encoded with the gzip
  60. method, as described in RFC 1952.
  61. """
  62. def __init__(self, response, limit=None):
  63. # response doesn't support tell() and read(), required by
  64. # GzipFile
  65. if not gzip:
  66. raise NotImplementedError
  67. self.limit = limit = limit if limit is not None else MAX_DATA
  68. if limit < 0: # no limit
  69. data = response.read()
  70. self.readlength = None
  71. else:
  72. data = response.read(limit + 1)
  73. self.readlength = 0
  74. if limit >= 0 and len(data) > limit:
  75. raise ValueError("max payload length exceeded")
  76. self.stringio = io.BytesIO(data)
  77. gzip.GzipFile.__init__(self, mode="rb", fileobj=self.stringio)
  78. def read(self, n):
  79. if self.limit >= 0:
  80. left = self.limit - self.readlength
  81. n = min(n, left + 1)
  82. data = gzip.GzipFile.read(self, n)
  83. self.readlength += len(data)
  84. if self.readlength > self.limit:
  85. raise ValueError("max payload length exceeded")
  86. return data
  87. else:
  88. return gzip.GzipFile.read(self, n)
  89. def close(self):
  90. gzip.GzipFile.close(self)
  91. self.stringio.close()
  92. class DefusedExpatParser(ExpatParser):
  93. def __init__(self, target, forbid_dtd=False, forbid_entities=True,
  94. forbid_external=True):
  95. ExpatParser.__init__(self, target)
  96. self.forbid_dtd = forbid_dtd
  97. self.forbid_entities = forbid_entities
  98. self.forbid_external = forbid_external
  99. parser = self._parser
  100. if self.forbid_dtd:
  101. parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl
  102. if self.forbid_entities:
  103. parser.EntityDeclHandler = self.defused_entity_decl
  104. parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl
  105. if self.forbid_external:
  106. parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler
  107. def defused_start_doctype_decl(self, name, sysid, pubid,
  108. has_internal_subset):
  109. raise DTDForbidden(name, sysid, pubid)
  110. def defused_entity_decl(self, name, is_parameter_entity, value, base,
  111. sysid, pubid, notation_name):
  112. raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
  113. def defused_unparsed_entity_decl(self, name, base, sysid, pubid,
  114. notation_name):
  115. # expat 1.2
  116. raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name)
  117. def defused_external_entity_ref_handler(self, context, base, sysid,
  118. pubid):
  119. raise ExternalReferenceForbidden(context, base, sysid, pubid)
  120. def monkey_patch():
  121. xmlrpc_client.FastParser = DefusedExpatParser
  122. xmlrpc_client.GzipDecodedResponse = DefusedGzipDecodedResponse
  123. xmlrpc_client.gzip_decode = defused_gzip_decode
  124. if xmlrpc_server:
  125. xmlrpc_server.gzip_decode = defused_gzip_decode
  126. def unmonkey_patch():
  127. xmlrpc_client.FastParser = None
  128. xmlrpc_client.GzipDecodedResponse = _OrigGzipDecodedResponse
  129. xmlrpc_client.gzip_decode = _orig_gzip_decode
  130. if xmlrpc_server:
  131. xmlrpc_server.gzip_decode = _orig_gzip_decode