Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """Generic MIME writer. |
2 | ||
3 | This module defines the class MimeWriter. The MimeWriter class implements | |
4 | a basic formatter for creating MIME multi-part files. It doesn't seek around | |
5 | the output file nor does it use large amounts of buffer space. You must write | |
6 | the parts out in the order that they should occur in the final file. | |
7 | MimeWriter does buffer the headers you add, allowing you to rearrange their | |
8 | order. | |
9 | ||
10 | """ | |
11 | ||
12 | ||
13 | import mimetools | |
14 | ||
15 | __all__ = ["MimeWriter"] | |
16 | ||
17 | class MimeWriter: | |
18 | ||
19 | """Generic MIME writer. | |
20 | ||
21 | Methods: | |
22 | ||
23 | __init__() | |
24 | addheader() | |
25 | flushheaders() | |
26 | startbody() | |
27 | startmultipartbody() | |
28 | nextpart() | |
29 | lastpart() | |
30 | ||
31 | A MIME writer is much more primitive than a MIME parser. It | |
32 | doesn't seek around on the output file, and it doesn't use large | |
33 | amounts of buffer space, so you have to write the parts in the | |
34 | order they should occur on the output file. It does buffer the | |
35 | headers you add, allowing you to rearrange their order. | |
36 | ||
37 | General usage is: | |
38 | ||
39 | f = <open the output file> | |
40 | w = MimeWriter(f) | |
41 | ...call w.addheader(key, value) 0 or more times... | |
42 | ||
43 | followed by either: | |
44 | ||
45 | f = w.startbody(content_type) | |
46 | ...call f.write(data) for body data... | |
47 | ||
48 | or: | |
49 | ||
50 | w.startmultipartbody(subtype) | |
51 | for each part: | |
52 | subwriter = w.nextpart() | |
53 | ...use the subwriter's methods to create the subpart... | |
54 | w.lastpart() | |
55 | ||
56 | The subwriter is another MimeWriter instance, and should be | |
57 | treated in the same way as the toplevel MimeWriter. This way, | |
58 | writing recursive body parts is easy. | |
59 | ||
60 | Warning: don't forget to call lastpart()! | |
61 | ||
62 | XXX There should be more state so calls made in the wrong order | |
63 | are detected. | |
64 | ||
65 | Some special cases: | |
66 | ||
67 | - startbody() just returns the file passed to the constructor; | |
68 | but don't use this knowledge, as it may be changed. | |
69 | ||
70 | - startmultipartbody() actually returns a file as well; | |
71 | this can be used to write the initial 'if you can read this your | |
72 | mailer is not MIME-aware' message. | |
73 | ||
74 | - If you call flushheaders(), the headers accumulated so far are | |
75 | written out (and forgotten); this is useful if you don't need a | |
76 | body part at all, e.g. for a subpart of type message/rfc822 | |
77 | that's (mis)used to store some header-like information. | |
78 | ||
79 | - Passing a keyword argument 'prefix=<flag>' to addheader(), | |
80 | start*body() affects where the header is inserted; 0 means | |
81 | append at the end, 1 means insert at the start; default is | |
82 | append for addheader(), but insert for start*body(), which use | |
83 | it to determine where the Content-Type header goes. | |
84 | ||
85 | """ | |
86 | ||
87 | def __init__(self, fp): | |
88 | self._fp = fp | |
89 | self._headers = [] | |
90 | ||
91 | def addheader(self, key, value, prefix=0): | |
92 | """Add a header line to the MIME message. | |
93 | ||
94 | The key is the name of the header, where the value obviously provides | |
95 | the value of the header. The optional argument prefix determines | |
96 | where the header is inserted; 0 means append at the end, 1 means | |
97 | insert at the start. The default is to append. | |
98 | ||
99 | """ | |
100 | lines = value.split("\n") | |
101 | while lines and not lines[-1]: del lines[-1] | |
102 | while lines and not lines[0]: del lines[0] | |
103 | for i in range(1, len(lines)): | |
104 | lines[i] = " " + lines[i].strip() | |
105 | value = "\n".join(lines) + "\n" | |
106 | line = key + ": " + value | |
107 | if prefix: | |
108 | self._headers.insert(0, line) | |
109 | else: | |
110 | self._headers.append(line) | |
111 | ||
112 | def flushheaders(self): | |
113 | """Writes out and forgets all headers accumulated so far. | |
114 | ||
115 | This is useful if you don't need a body part at all; for example, | |
116 | for a subpart of type message/rfc822 that's (mis)used to store some | |
117 | header-like information. | |
118 | ||
119 | """ | |
120 | self._fp.writelines(self._headers) | |
121 | self._headers = [] | |
122 | ||
123 | def startbody(self, ctype, plist=[], prefix=1): | |
124 | """Returns a file-like object for writing the body of the message. | |
125 | ||
126 | The content-type is set to the provided ctype, and the optional | |
127 | parameter, plist, provides additional parameters for the | |
128 | content-type declaration. The optional argument prefix determines | |
129 | where the header is inserted; 0 means append at the end, 1 means | |
130 | insert at the start. The default is to insert at the start. | |
131 | ||
132 | """ | |
133 | for name, value in plist: | |
134 | ctype = ctype + ';\n %s=\"%s\"' % (name, value) | |
135 | self.addheader("Content-Type", ctype, prefix=prefix) | |
136 | self.flushheaders() | |
137 | self._fp.write("\n") | |
138 | return self._fp | |
139 | ||
140 | def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1): | |
141 | """Returns a file-like object for writing the body of the message. | |
142 | ||
143 | Additionally, this method initializes the multi-part code, where the | |
144 | subtype parameter provides the multipart subtype, the boundary | |
145 | parameter may provide a user-defined boundary specification, and the | |
146 | plist parameter provides optional parameters for the subtype. The | |
147 | optional argument, prefix, determines where the header is inserted; | |
148 | 0 means append at the end, 1 means insert at the start. The default | |
149 | is to insert at the start. Subparts should be created using the | |
150 | nextpart() method. | |
151 | ||
152 | """ | |
153 | self._boundary = boundary or mimetools.choose_boundary() | |
154 | return self.startbody("multipart/" + subtype, | |
155 | [("boundary", self._boundary)] + plist, | |
156 | prefix=prefix) | |
157 | ||
158 | def nextpart(self): | |
159 | """Returns a new instance of MimeWriter which represents an | |
160 | individual part in a multipart message. | |
161 | ||
162 | This may be used to write the part as well as used for creating | |
163 | recursively complex multipart messages. The message must first be | |
164 | initialized with the startmultipartbody() method before using the | |
165 | nextpart() method. | |
166 | ||
167 | """ | |
168 | self._fp.write("\n--" + self._boundary + "\n") | |
169 | return self.__class__(self._fp) | |
170 | ||
171 | def lastpart(self): | |
172 | """This is used to designate the last part of a multipart message. | |
173 | ||
174 | It should always be used when writing multipart messages. | |
175 | ||
176 | """ | |
177 | self._fp.write("\n--" + self._boundary + "--\n") | |
178 | ||
179 | ||
180 | if __name__ == '__main__': | |
181 | import test.test_MimeWriter |