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