summaryrefslogtreecommitdiff
path: root/django/core/files/uploadedfile.py
blob: 51cec172d40b5511db3d47c4febf8936fcd18e4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""
Classes representing uploaded files.
"""

import os
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')

class UploadedFile(object):
    """
    A abstract uploadded file (``TemporaryUploadedFile`` and
    ``InMemoryUploadedFile`` are the built-in concrete subclasses).

    An ``UploadedFile`` object behaves somewhat like a file object and
    represents some file data that the user submitted with a form.
    """
    DEFAULT_CHUNK_SIZE = 64 * 2**10

    def __init__(self, file_name=None, content_type=None, file_size=None, charset=None):
        self.file_name = file_name
        self.file_size = file_size
        self.content_type = content_type
        self.charset = charset

    def __repr__(self):
        return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)

    def _set_file_name(self, name):
        # Sanitize the file name so that it can't be dangerous.
        if name is not None:
            # Just use the basename of the file -- anything else is dangerous.
            name = os.path.basename(name)
            
            # File names longer than 255 characters can cause problems on older OSes.
            if len(name) > 255:
                name, ext = os.path.splitext(name)
                name = name[:255 - len(ext)] + ext
                
        self._file_name = name
        
    def _get_file_name(self):
        return self._file_name
        
    file_name = property(_get_file_name, _set_file_name)

    def chunk(self, chunk_size=None):
        """
        Read the file and yield chucks of ``chunk_size`` bytes (defaults to
        ``UploadedFile.DEFAULT_CHUNK_SIZE``).
        """
        if not chunk_size:
            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE

        if hasattr(self, 'seek'):
            self.seek(0)
        # Assume the pointer is at zero...
        counter = self.file_size

        while counter > 0:
            yield self.read(chunk_size)
            counter -= chunk_size

    def multiple_chunks(self, chunk_size=None):
        """
        Returns ``True`` if you can expect multiple chunks.

        NB: If a particular file representation is in memory, subclasses should
        always return ``False`` -- there's no good reason to read from memory in
        chunks.
        """
        if not chunk_size:
            chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
        return self.file_size < chunk_size

    # Abstract methods; subclasses *must* default read() and probably should
    # define open/close.
    def read(self, num_bytes=None):
        raise NotImplementedError()

    def open(self):
        pass

    def close(self):
        pass

    # Backwards-compatible support for uploaded-files-as-dictionaries.
    def __getitem__(self, key):
        import warnings
        warnings.warn(
            message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.",
            category = DeprecationWarning,
            stacklevel = 2
        )
        backwards_translate = {
            'filename': 'file_name',
            'content-type': 'content_type',
            }

        if key == 'content':
            return self.read()
        elif key == 'filename':
            return self.file_name
        elif key == 'content-type':
            return self.content_type
        else:
            return getattr(self, key)

class TemporaryUploadedFile(UploadedFile):
    """
    A file uploaded to a temporary location (i.e. stream-to-disk).
    """

    def __init__(self, file, file_name, content_type, file_size, charset):
        super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
        self.file = file
        self.path = file.name
        self.file.seek(0)

    def temporary_file_path(self):
        """
        Returns the full path of this file.
        """
        return self.path

    def read(self, *args, **kwargs):
        return self.file.read(*args, **kwargs)

    def open(self):
        self.seek(0)

    def seek(self, *args, **kwargs):
        self.file.seek(*args, **kwargs)

class InMemoryUploadedFile(UploadedFile):
    """
    A file uploaded into memory (i.e. stream-to-memory).
    """
    def __init__(self, file, field_name, file_name, content_type, charset, file_size):
        super(InMemoryUploadedFile, self).__init__(file_name, content_type, charset, file_size)
        self.file = file
        self.field_name = field_name
        self.file.seek(0)

    def seek(self, *args, **kwargs):
        self.file.seek(*args, **kwargs)

    def open(self):
        self.seek(0)

    def read(self, *args, **kwargs):
        return self.file.read(*args, **kwargs)

    def chunk(self, chunk_size=None):
        self.file.seek(0)
        yield self.read()

    def multiple_chunks(self, chunk_size=None):
        # Since it's in memory, we'll never have multiple chunks.
        return False

class SimpleUploadedFile(InMemoryUploadedFile):
    """
    A simple representation of a file, which just has content, size, and a name.
    """
    def __init__(self, name, content, content_type='text/plain'):
        self.file = StringIO(content or '')
        self.file_name = name
        self.field_name = None
        self.file_size = len(content or '')
        self.content_type = content_type
        self.charset = None
        self.file.seek(0)

    def from_dict(cls, file_dict):
        """
        Creates a SimpleUploadedFile object from
        a dictionary object with the following keys:
           - filename
           - content-type
           - content
        """
        return cls(file_dict['filename'],
                   file_dict['content'],
                   file_dict.get('content-type', 'text/plain'))

    from_dict = classmethod(from_dict)