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.

uploadhandler.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. """
  2. Base file upload handler classes, and the built-in concrete subclasses
  3. """
  4. from io import BytesIO
  5. from django.conf import settings
  6. from django.core.files.uploadedfile import (
  7. InMemoryUploadedFile, TemporaryUploadedFile,
  8. )
  9. from django.utils.module_loading import import_string
  10. __all__ = [
  11. 'UploadFileException', 'StopUpload', 'SkipFile', 'FileUploadHandler',
  12. 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', 'load_handler',
  13. 'StopFutureHandlers'
  14. ]
  15. class UploadFileException(Exception):
  16. """
  17. Any error having to do with uploading files.
  18. """
  19. pass
  20. class StopUpload(UploadFileException):
  21. """
  22. This exception is raised when an upload must abort.
  23. """
  24. def __init__(self, connection_reset=False):
  25. """
  26. If ``connection_reset`` is ``True``, Django knows will halt the upload
  27. without consuming the rest of the upload. This will cause the browser to
  28. show a "connection reset" error.
  29. """
  30. self.connection_reset = connection_reset
  31. def __str__(self):
  32. if self.connection_reset:
  33. return 'StopUpload: Halt current upload.'
  34. else:
  35. return 'StopUpload: Consume request data, then halt.'
  36. class SkipFile(UploadFileException):
  37. """
  38. This exception is raised by an upload handler that wants to skip a given file.
  39. """
  40. pass
  41. class StopFutureHandlers(UploadFileException):
  42. """
  43. Upload handers that have handled a file and do not want future handlers to
  44. run should raise this exception instead of returning None.
  45. """
  46. pass
  47. class FileUploadHandler:
  48. """
  49. Base class for streaming upload handlers.
  50. """
  51. chunk_size = 64 * 2 ** 10 # : The default chunk size is 64 KB.
  52. def __init__(self, request=None):
  53. self.file_name = None
  54. self.content_type = None
  55. self.content_length = None
  56. self.charset = None
  57. self.content_type_extra = None
  58. self.request = request
  59. def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
  60. """
  61. Handle the raw input from the client.
  62. Parameters:
  63. :input_data:
  64. An object that supports reading via .read().
  65. :META:
  66. ``request.META``.
  67. :content_length:
  68. The (integer) value of the Content-Length header from the
  69. client.
  70. :boundary: The boundary from the Content-Type header. Be sure to
  71. prepend two '--'.
  72. """
  73. pass
  74. def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
  75. """
  76. Signal that a new file has been started.
  77. Warning: As with any data from the client, you should not trust
  78. content_length (and sometimes won't even get it).
  79. """
  80. self.field_name = field_name
  81. self.file_name = file_name
  82. self.content_type = content_type
  83. self.content_length = content_length
  84. self.charset = charset
  85. self.content_type_extra = content_type_extra
  86. def receive_data_chunk(self, raw_data, start):
  87. """
  88. Receive data from the streamed upload parser. ``start`` is the position
  89. in the file of the chunk.
  90. """
  91. raise NotImplementedError('subclasses of FileUploadHandler must provide a receive_data_chunk() method')
  92. def file_complete(self, file_size):
  93. """
  94. Signal that a file has completed. File size corresponds to the actual
  95. size accumulated by all the chunks.
  96. Subclasses should return a valid ``UploadedFile`` object.
  97. """
  98. raise NotImplementedError('subclasses of FileUploadHandler must provide a file_complete() method')
  99. def upload_complete(self):
  100. """
  101. Signal that the upload is complete. Subclasses should perform cleanup
  102. that is necessary for this handler.
  103. """
  104. pass
  105. class TemporaryFileUploadHandler(FileUploadHandler):
  106. """
  107. Upload handler that streams data into a temporary file.
  108. """
  109. def new_file(self, *args, **kwargs):
  110. """
  111. Create the file object to append to as data is coming in.
  112. """
  113. super().new_file(*args, **kwargs)
  114. self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra)
  115. def receive_data_chunk(self, raw_data, start):
  116. self.file.write(raw_data)
  117. def file_complete(self, file_size):
  118. self.file.seek(0)
  119. self.file.size = file_size
  120. return self.file
  121. class MemoryFileUploadHandler(FileUploadHandler):
  122. """
  123. File upload handler to stream uploads into memory (used for small files).
  124. """
  125. def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
  126. """
  127. Use the content_length to signal whether or not this handler should be
  128. used.
  129. """
  130. # Check the content-length header to see if we should
  131. # If the post is too large, we cannot use the Memory handler.
  132. self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE
  133. def new_file(self, *args, **kwargs):
  134. super().new_file(*args, **kwargs)
  135. if self.activated:
  136. self.file = BytesIO()
  137. raise StopFutureHandlers()
  138. def receive_data_chunk(self, raw_data, start):
  139. """Add the data to the BytesIO file."""
  140. if self.activated:
  141. self.file.write(raw_data)
  142. else:
  143. return raw_data
  144. def file_complete(self, file_size):
  145. """Return a file object if this handler is activated."""
  146. if not self.activated:
  147. return
  148. self.file.seek(0)
  149. return InMemoryUploadedFile(
  150. file=self.file,
  151. field_name=self.field_name,
  152. name=self.file_name,
  153. content_type=self.content_type,
  154. size=file_size,
  155. charset=self.charset,
  156. content_type_extra=self.content_type_extra
  157. )
  158. def load_handler(path, *args, **kwargs):
  159. """
  160. Given a path to a handler, return an instance of that handler.
  161. E.g.::
  162. >>> from django.http import HttpRequest
  163. >>> request = HttpRequest()
  164. >>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request)
  165. <TemporaryFileUploadHandler object at 0x...>
  166. """
  167. return import_string(path)(*args, **kwargs)