summaryrefslogtreecommitdiffstats
path: root/data/multipart.py
blob: 836b968af909cd75564ec31573254ae951e25f71 (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
'''
Classes for using multipart form data from Python, which does not (at the
time of writing) support this directly.

To use this, make an instance of Multipart and add parts to it via the factory
methods field and file.  When you are done, get the content via the get method.

@author: Stacy Prowell (http://stacyprowell.com)
'''

import mimetypes


class Part(object):
    '''
    Class holding a single part of the form.  You should never need to use
    this class directly; instead, use the factory methods in Multipart:
    field and file.
    '''

    # The boundary to use.  This is shamelessly taken from the standard.
    BOUNDARY = '----------AaB03x'
    CRLF = '\r\n'
    # Common headers.
    CONTENT_TYPE = 'Content-Type'
    CONTENT_DISPOSITION = 'Content-Disposition'
    # The default content type for parts.
    DEFAULT_CONTENT_TYPE = 'application/octet-stream'

    def __init__(self, name, filename, body, headers):
        '''
        Make a new part.  The part will have the given headers added initially.

        @param name: The part name.
        @type name: str
        @param filename: If this is a file, the name of the file.  Otherwise
                        None.
        @type filename: str
        @param body: The body of the part.
        @type body: str
        @param headers: Additional headers, or overrides, for this part.
                        You can override Content-Type here.
        @type headers: dict
        '''
        self._headers = headers.copy()
        self._name = name
        self._filename = filename
        self._body = body
        # We respect any content type passed in, but otherwise set it here.
        # We set the content disposition now, overwriting any prior value.
        if self._filename == None:
            self._headers[Part.CONTENT_DISPOSITION] = \
                ('form-data; name="%s"' % self._name)
            self._headers.setdefault(Part.CONTENT_TYPE,
                                     Part.DEFAULT_CONTENT_TYPE)
        else:
            self._headers[Part.CONTENT_DISPOSITION] = \
                ('form-data; name="%s"; filename="%s"' %
                 (self._name, self._filename))
            self._headers.setdefault(Part.CONTENT_TYPE,
                                     mimetypes.guess_type(filename)[0]
                                     or Part.DEFAULT_CONTENT_TYPE)
        return

    def get(self):
        '''
        Convert the part into a list of lines for output.  This includes
        the boundary lines, part header lines, and the part itself.  A
        blank line is included between the header and the body.

        @return: Lines of this part.
        @rtype: list
        '''
        lines = []
        lines.append('--' + Part.BOUNDARY)
        for (key, val) in self._headers.items():
            lines.append(str('%s: %s' % (key, val)))
        lines.append('')
        lines.append(self._body)
        return lines


class Multipart(object):
    '''
    Encapsulate multipart form data.  To use this, make an instance and then
    add parts to it via the two methods (field and file).  When done, you can
    get the result via the get method.

    See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for
    details on multipart/form-data.

    Watch http://bugs.python.org/issue3244 to see if this is fixed in the
    Python libraries.

    @return: content type, body
    @rtype: tuple
    '''

    def __init__(self):
        self.parts = []
        return

    def field(self, name, value, headers={}):
        '''
        Create and append a field part.  This kind of part has a field name
        and value.

        @param name: The field name.
        @type name: str
        @param value: The field value.
        @type value: str
        @param headers: Headers to set in addition to disposition.
        @type headers: dict
        '''
        self.parts.append(Part(name, None, value, headers))
        return

    def file(self, name, filename, value, headers={}):
        '''
        Create and append a file part.  THis kind of part has a field name,
        a filename, and a value.

        @param name: The field name.
        @type name: str
        @param value: The field value.
        @type value: str
        @param headers: Headers to set in addition to disposition.
        @type headers: dict
        '''
        self.parts.append(Part(name, filename, value, headers))
        return

    def get(self):
        '''
        Get the multipart form data.  This returns the content type, which
        specifies the boundary marker, and also returns the body containing
        all parts and bondary markers.

        @return: content type, body
        @rtype: tuple
        '''
        all = []
        for part in self.parts:
            all += part.get()
        all.append('--' + Part.BOUNDARY + '--')
        all.append('')
        # We have to return the content type, since it specifies the boundary.
        content_type = 'multipart/form-data; boundary=%s' % Part.BOUNDARY
        return content_type, Part.CRLF.join(all)