1"""
2Classes representing uploaded files.
3"""
4
5import os
6from io import BytesIO
7
8from django.conf import settings
9from django.core.files import temp as tempfile
10from django.core.files.base import File
11from django.core.files.utils import validate_file_name
12
13__all__ = (
14 "UploadedFile",
15 "TemporaryUploadedFile",
16 "InMemoryUploadedFile",
17 "SimpleUploadedFile",
18)
19
20
21class UploadedFile(File):
22 """
23 An abstract uploaded file (``TemporaryUploadedFile`` and
24 ``InMemoryUploadedFile`` are the built-in concrete subclasses).
25
26 An ``UploadedFile`` object behaves somewhat like a file object and
27 represents some file data that the user submitted with a form.
28 """
29
30 def __init__(
31 self,
32 file=None,
33 name=None,
34 content_type=None,
35 size=None,
36 charset=None,
37 content_type_extra=None,
38 ):
39 super().__init__(file, name)
40 self.size = size
41 self.content_type = content_type
42 self.charset = charset
43 self.content_type_extra = content_type_extra
44
45 def __repr__(self):
46 return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)
47
48 def _get_name(self):
49 return self._name
50
51 def _set_name(self, name):
52 # Sanitize the file name so that it can't be dangerous.
53 if name is not None:
54 # Just use the basename of the file -- anything else is dangerous.
55 name = os.path.basename(name)
56
57 # File names longer than 255 characters can cause problems on older OSes.
58 if len(name) > 255:
59 name, ext = os.path.splitext(name)
60 ext = ext[:255]
61 name = name[: 255 - len(ext)] + ext
62
63 name = validate_file_name(name)
64
65 self._name = name
66
67 name = property(_get_name, _set_name)
68
69
70class TemporaryUploadedFile(UploadedFile):
71 """
72 A file uploaded to a temporary location (i.e. stream-to-disk).
73 """
74
75 def __init__(self, name, content_type, size, charset, content_type_extra=None):
76 _, ext = os.path.splitext(name)
77 file = tempfile.NamedTemporaryFile(
78 suffix=".upload" + ext, dir=settings.FILE_UPLOAD_TEMP_DIR
79 )
80 super().__init__(file, name, content_type, size, charset, content_type_extra)
81
82 def temporary_file_path(self):
83 """Return the full path of this file."""
84 return self.file.name
85
86 def close(self):
87 try:
88 return self.file.close()
89 except FileNotFoundError:
90 # The file was moved or deleted before the tempfile could unlink
91 # it. Still sets self.file.close_called and calls
92 # self.file.file.close() before the exception.
93 pass
94
95
96class InMemoryUploadedFile(UploadedFile):
97 """
98 A file uploaded into memory (i.e. stream-to-memory).
99 """
100
101 def __init__(
102 self,
103 file,
104 field_name,
105 name,
106 content_type,
107 size,
108 charset,
109 content_type_extra=None,
110 ):
111 super().__init__(file, name, content_type, size, charset, content_type_extra)
112 self.field_name = field_name
113
114 def open(self, mode=None):
115 self.file.seek(0)
116 return self
117
118 def chunks(self, chunk_size=None):
119 self.file.seek(0)
120 yield self.read()
121
122 def multiple_chunks(self, chunk_size=None):
123 # Since it's in memory, we'll never have multiple chunks.
124 return False
125
126
127class SimpleUploadedFile(InMemoryUploadedFile):
128 """
129 A simple representation of a file, which just has content, size, and a name.
130 """
131
132 def __init__(self, name, content, content_type="text/plain"):
133 content = content or b""
134 super().__init__(
135 BytesIO(content), None, name, content_type, len(content), None, None
136 )
137
138 @classmethod
139 def from_dict(cls, file_dict):
140 """
141 Create a SimpleUploadedFile object from a dictionary with keys:
142 - filename
143 - content-type
144 - content
145 """
146 return cls(
147 file_dict["filename"],
148 file_dict["content"],
149 file_dict.get("content-type", "text/plain"),
150 )