Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

186 righe
6.5 KiB

  1. import base64
  2. import binascii
  3. from typing import TYPE_CHECKING
  4. from .exceptions import DecodeError
  5. if TYPE_CHECKING: # pragma: no cover
  6. from typing import Protocol, TypeVar
  7. _T_contra = TypeVar("_T_contra", contravariant=True)
  8. class SupportsWrite(Protocol[_T_contra]):
  9. def write(self, __b: _T_contra) -> object: ...
  10. # No way to specify optional methods. See
  11. # https://github.com/python/typing/issues/601
  12. # close() [Optional]
  13. # finalize() [Optional]
  14. class Base64Decoder:
  15. """This object provides an interface to decode a stream of Base64 data. It
  16. is instantiated with an "underlying object", and whenever a write()
  17. operation is performed, it will decode the incoming data as Base64, and
  18. call write() on the underlying object. This is primarily used for decoding
  19. form data encoded as Base64, but can be used for other purposes::
  20. from python_multipart.decoders import Base64Decoder
  21. fd = open("notb64.txt", "wb")
  22. decoder = Base64Decoder(fd)
  23. try:
  24. decoder.write("Zm9vYmFy") # "foobar" in Base64
  25. decoder.finalize()
  26. finally:
  27. decoder.close()
  28. # The contents of "notb64.txt" should be "foobar".
  29. This object will also pass all finalize() and close() calls to the
  30. underlying object, if the underlying object supports them.
  31. Note that this class maintains a cache of base64 chunks, so that a write of
  32. arbitrary size can be performed. You must call :meth:`finalize` on this
  33. object after all writes are completed to ensure that all data is flushed
  34. to the underlying object.
  35. :param underlying: the underlying object to pass writes to
  36. """
  37. def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
  38. self.cache = bytearray()
  39. self.underlying = underlying
  40. def write(self, data: bytes) -> int:
  41. """Takes any input data provided, decodes it as base64, and passes it
  42. on to the underlying object. If the data provided is invalid base64
  43. data, then this method will raise
  44. a :class:`python_multipart.exceptions.DecodeError`
  45. :param data: base64 data to decode
  46. """
  47. # Prepend any cache info to our data.
  48. if len(self.cache) > 0:
  49. data = self.cache + data
  50. # Slice off a string that's a multiple of 4.
  51. decode_len = (len(data) // 4) * 4
  52. val = data[:decode_len]
  53. # Decode and write, if we have any.
  54. if len(val) > 0:
  55. try:
  56. decoded = base64.b64decode(val)
  57. except binascii.Error:
  58. raise DecodeError("There was an error raised while decoding base64-encoded data.")
  59. self.underlying.write(decoded)
  60. # Get the remaining bytes and save in our cache.
  61. remaining_len = len(data) % 4
  62. if remaining_len > 0:
  63. self.cache[:] = data[-remaining_len:]
  64. else:
  65. self.cache[:] = b""
  66. # Return the length of the data to indicate no error.
  67. return len(data)
  68. def close(self) -> None:
  69. """Close this decoder. If the underlying object has a `close()`
  70. method, this function will call it.
  71. """
  72. if hasattr(self.underlying, "close"):
  73. self.underlying.close()
  74. def finalize(self) -> None:
  75. """Finalize this object. This should be called when no more data
  76. should be written to the stream. This function can raise a
  77. :class:`python_multipart.exceptions.DecodeError` if there is some remaining
  78. data in the cache.
  79. If the underlying object has a `finalize()` method, this function will
  80. call it.
  81. """
  82. if len(self.cache) > 0:
  83. raise DecodeError(
  84. "There are %d bytes remaining in the Base64Decoder cache when finalize() is called" % len(self.cache)
  85. )
  86. if hasattr(self.underlying, "finalize"):
  87. self.underlying.finalize()
  88. def __repr__(self) -> str:
  89. return f"{self.__class__.__name__}(underlying={self.underlying!r})"
  90. class QuotedPrintableDecoder:
  91. """This object provides an interface to decode a stream of quoted-printable
  92. data. It is instantiated with an "underlying object", in the same manner
  93. as the :class:`python_multipart.decoders.Base64Decoder` class. This class behaves
  94. in exactly the same way, including maintaining a cache of quoted-printable
  95. chunks.
  96. :param underlying: the underlying object to pass writes to
  97. """
  98. def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
  99. self.cache = b""
  100. self.underlying = underlying
  101. def write(self, data: bytes) -> int:
  102. """Takes any input data provided, decodes it as quoted-printable, and
  103. passes it on to the underlying object.
  104. :param data: quoted-printable data to decode
  105. """
  106. # Prepend any cache info to our data.
  107. if len(self.cache) > 0:
  108. data = self.cache + data
  109. # If the last 2 characters have an '=' sign in it, then we won't be
  110. # able to decode the encoded value and we'll need to save it for the
  111. # next decoding step.
  112. if data[-2:].find(b"=") != -1:
  113. enc, rest = data[:-2], data[-2:]
  114. else:
  115. enc = data
  116. rest = b""
  117. # Encode and write, if we have data.
  118. if len(enc) > 0:
  119. self.underlying.write(binascii.a2b_qp(enc))
  120. # Save remaining in cache.
  121. self.cache = rest
  122. return len(data)
  123. def close(self) -> None:
  124. """Close this decoder. If the underlying object has a `close()`
  125. method, this function will call it.
  126. """
  127. if hasattr(self.underlying, "close"):
  128. self.underlying.close()
  129. def finalize(self) -> None:
  130. """Finalize this object. This should be called when no more data
  131. should be written to the stream. This function will not raise any
  132. exceptions, but it may write more data to the underlying object if
  133. there is data remaining in the cache.
  134. If the underlying object has a `finalize()` method, this function will
  135. call it.
  136. """
  137. # If we have a cache, write and then remove it.
  138. if len(self.cache) > 0: # pragma: no cover
  139. self.underlying.write(binascii.a2b_qp(self.cache))
  140. self.cache = b""
  141. # Finalize our underlying stream.
  142. if hasattr(self.underlying, "finalize"):
  143. self.underlying.finalize()
  144. def __repr__(self) -> str:
  145. return f"{self.__class__.__name__}(underlying={self.underlying!r})"