PageRenderTime 42ms CodeModel.GetById 11ms app.highlight 25ms RepoModel.GetById 2ms app.codeStats 0ms

/lib-python/2.7/mimify.py

https://bitbucket.org/evelyn559/pypy
Python | 468 lines | 432 code | 10 blank | 26 comment | 12 complexity | 4bb88519bd81f14d8c0583cf4be1a832 MD5 | raw file
  1#! /usr/bin/env python
  2
  3"""Mimification and unmimification of mail messages.
  4
  5Decode quoted-printable parts of a mail message or encode using
  6quoted-printable.
  7
  8Usage:
  9        mimify(input, output)
 10        unmimify(input, output, decode_base64 = 0)
 11to encode and decode respectively.  Input and output may be the name
 12of a file or an open file object.  Only a readline() method is used
 13on the input file, only a write() method is used on the output file.
 14When using file names, the input and output file names may be the
 15same.
 16
 17Interactive usage:
 18        mimify.py -e [infile [outfile]]
 19        mimify.py -d [infile [outfile]]
 20to encode and decode respectively.  Infile defaults to standard
 21input and outfile to standard output.
 22"""
 23
 24# Configure
 25MAXLEN = 200    # if lines longer than this, encode as quoted-printable
 26CHARSET = 'ISO-8859-1'  # default charset for non-US-ASCII mail
 27QUOTE = '> '            # string replies are quoted with
 28# End configure
 29
 30import re
 31
 32import warnings
 33warnings.warn("the mimify module is deprecated; use the email package instead",
 34                DeprecationWarning, 2)
 35
 36__all__ = ["mimify","unmimify","mime_encode_header","mime_decode_header"]
 37
 38qp = re.compile('^content-transfer-encoding:\\s*quoted-printable', re.I)
 39base64_re = re.compile('^content-transfer-encoding:\\s*base64', re.I)
 40mp = re.compile('^content-type:.*multipart/.*boundary="?([^;"\n]*)', re.I|re.S)
 41chrset = re.compile('^(content-type:.*charset=")(us-ascii|iso-8859-[0-9]+)(".*)', re.I|re.S)
 42he = re.compile('^-*\n')
 43mime_code = re.compile('=([0-9a-f][0-9a-f])', re.I)
 44mime_head = re.compile('=\\?iso-8859-1\\?q\\?([^? \t\n]+)\\?=', re.I)
 45repl = re.compile('^subject:\\s+re: ', re.I)
 46
 47class File:
 48    """A simple fake file object that knows about limited read-ahead and
 49    boundaries.  The only supported method is readline()."""
 50
 51    def __init__(self, file, boundary):
 52        self.file = file
 53        self.boundary = boundary
 54        self.peek = None
 55
 56    def readline(self):
 57        if self.peek is not None:
 58            return ''
 59        line = self.file.readline()
 60        if not line:
 61            return line
 62        if self.boundary:
 63            if line == self.boundary + '\n':
 64                self.peek = line
 65                return ''
 66            if line == self.boundary + '--\n':
 67                self.peek = line
 68                return ''
 69        return line
 70
 71class HeaderFile:
 72    def __init__(self, file):
 73        self.file = file
 74        self.peek = None
 75
 76    def readline(self):
 77        if self.peek is not None:
 78            line = self.peek
 79            self.peek = None
 80        else:
 81            line = self.file.readline()
 82        if not line:
 83            return line
 84        if he.match(line):
 85            return line
 86        while 1:
 87            self.peek = self.file.readline()
 88            if len(self.peek) == 0 or \
 89               (self.peek[0] != ' ' and self.peek[0] != '\t'):
 90                return line
 91            line = line + self.peek
 92            self.peek = None
 93
 94def mime_decode(line):
 95    """Decode a single line of quoted-printable text to 8bit."""
 96    newline = ''
 97    pos = 0
 98    while 1:
 99        res = mime_code.search(line, pos)
100        if res is None:
101            break
102        newline = newline + line[pos:res.start(0)] + \
103                  chr(int(res.group(1), 16))
104        pos = res.end(0)
105    return newline + line[pos:]
106
107def mime_decode_header(line):
108    """Decode a header line to 8bit."""
109    newline = ''
110    pos = 0
111    while 1:
112        res = mime_head.search(line, pos)
113        if res is None:
114            break
115        match = res.group(1)
116        # convert underscores to spaces (before =XX conversion!)
117        match = ' '.join(match.split('_'))
118        newline = newline + line[pos:res.start(0)] + mime_decode(match)
119        pos = res.end(0)
120    return newline + line[pos:]
121
122def unmimify_part(ifile, ofile, decode_base64 = 0):
123    """Convert a quoted-printable part of a MIME mail message to 8bit."""
124    multipart = None
125    quoted_printable = 0
126    is_base64 = 0
127    is_repl = 0
128    if ifile.boundary and ifile.boundary[:2] == QUOTE:
129        prefix = QUOTE
130    else:
131        prefix = ''
132
133    # read header
134    hfile = HeaderFile(ifile)
135    while 1:
136        line = hfile.readline()
137        if not line:
138            return
139        if prefix and line[:len(prefix)] == prefix:
140            line = line[len(prefix):]
141            pref = prefix
142        else:
143            pref = ''
144        line = mime_decode_header(line)
145        if qp.match(line):
146            quoted_printable = 1
147            continue        # skip this header
148        if decode_base64 and base64_re.match(line):
149            is_base64 = 1
150            continue
151        ofile.write(pref + line)
152        if not prefix and repl.match(line):
153            # we're dealing with a reply message
154            is_repl = 1
155        mp_res = mp.match(line)
156        if mp_res:
157            multipart = '--' + mp_res.group(1)
158        if he.match(line):
159            break
160    if is_repl and (quoted_printable or multipart):
161        is_repl = 0
162
163    # read body
164    while 1:
165        line = ifile.readline()
166        if not line:
167            return
168        line = re.sub(mime_head, '\\1', line)
169        if prefix and line[:len(prefix)] == prefix:
170            line = line[len(prefix):]
171            pref = prefix
172        else:
173            pref = ''
174##              if is_repl and len(line) >= 4 and line[:4] == QUOTE+'--' and line[-3:] != '--\n':
175##                      multipart = line[:-1]
176        while multipart:
177            if line == multipart + '--\n':
178                ofile.write(pref + line)
179                multipart = None
180                line = None
181                break
182            if line == multipart + '\n':
183                ofile.write(pref + line)
184                nifile = File(ifile, multipart)
185                unmimify_part(nifile, ofile, decode_base64)
186                line = nifile.peek
187                if not line:
188                    # premature end of file
189                    break
190                continue
191            # not a boundary between parts
192            break
193        if line and quoted_printable:
194            while line[-2:] == '=\n':
195                line = line[:-2]
196                newline = ifile.readline()
197                if newline[:len(QUOTE)] == QUOTE:
198                    newline = newline[len(QUOTE):]
199                line = line + newline
200            line = mime_decode(line)
201        if line and is_base64 and not pref:
202            import base64
203            line = base64.decodestring(line)
204        if line:
205            ofile.write(pref + line)
206
207def unmimify(infile, outfile, decode_base64 = 0):
208    """Convert quoted-printable parts of a MIME mail message to 8bit."""
209    if type(infile) == type(''):
210        ifile = open(infile)
211        if type(outfile) == type('') and infile == outfile:
212            import os
213            d, f = os.path.split(infile)
214            os.rename(infile, os.path.join(d, ',' + f))
215    else:
216        ifile = infile
217    if type(outfile) == type(''):
218        ofile = open(outfile, 'w')
219    else:
220        ofile = outfile
221    nifile = File(ifile, None)
222    unmimify_part(nifile, ofile, decode_base64)
223    ofile.flush()
224
225mime_char = re.compile('[=\177-\377]') # quote these chars in body
226mime_header_char = re.compile('[=?\177-\377]') # quote these in header
227
228def mime_encode(line, header):
229    """Code a single line as quoted-printable.
230    If header is set, quote some extra characters."""
231    if header:
232        reg = mime_header_char
233    else:
234        reg = mime_char
235    newline = ''
236    pos = 0
237    if len(line) >= 5 and line[:5] == 'From ':
238        # quote 'From ' at the start of a line for stupid mailers
239        newline = ('=%02x' % ord('F')).upper()
240        pos = 1
241    while 1:
242        res = reg.search(line, pos)
243        if res is None:
244            break
245        newline = newline + line[pos:res.start(0)] + \
246                  ('=%02x' % ord(res.group(0))).upper()
247        pos = res.end(0)
248    line = newline + line[pos:]
249
250    newline = ''
251    while len(line) >= 75:
252        i = 73
253        while line[i] == '=' or line[i-1] == '=':
254            i = i - 1
255        i = i + 1
256        newline = newline + line[:i] + '=\n'
257        line = line[i:]
258    return newline + line
259
260mime_header = re.compile('([ \t(]|^)([-a-zA-Z0-9_+]*[\177-\377][-a-zA-Z0-9_+\177-\377]*)(?=[ \t)]|\n)')
261
262def mime_encode_header(line):
263    """Code a single header line as quoted-printable."""
264    newline = ''
265    pos = 0
266    while 1:
267        res = mime_header.search(line, pos)
268        if res is None:
269            break
270        newline = '%s%s%s=?%s?Q?%s?=' % \
271                  (newline, line[pos:res.start(0)], res.group(1),
272                   CHARSET, mime_encode(res.group(2), 1))
273        pos = res.end(0)
274    return newline + line[pos:]
275
276mv = re.compile('^mime-version:', re.I)
277cte = re.compile('^content-transfer-encoding:', re.I)
278iso_char = re.compile('[\177-\377]')
279
280def mimify_part(ifile, ofile, is_mime):
281    """Convert an 8bit part of a MIME mail message to quoted-printable."""
282    has_cte = is_qp = is_base64 = 0
283    multipart = None
284    must_quote_body = must_quote_header = has_iso_chars = 0
285
286    header = []
287    header_end = ''
288    message = []
289    message_end = ''
290    # read header
291    hfile = HeaderFile(ifile)
292    while 1:
293        line = hfile.readline()
294        if not line:
295            break
296        if not must_quote_header and iso_char.search(line):
297            must_quote_header = 1
298        if mv.match(line):
299            is_mime = 1
300        if cte.match(line):
301            has_cte = 1
302            if qp.match(line):
303                is_qp = 1
304            elif base64_re.match(line):
305                is_base64 = 1
306        mp_res = mp.match(line)
307        if mp_res:
308            multipart = '--' + mp_res.group(1)
309        if he.match(line):
310            header_end = line
311            break
312        header.append(line)
313
314    # read body
315    while 1:
316        line = ifile.readline()
317        if not line:
318            break
319        if multipart:
320            if line == multipart + '--\n':
321                message_end = line
322                break
323            if line == multipart + '\n':
324                message_end = line
325                break
326        if is_base64:
327            message.append(line)
328            continue
329        if is_qp:
330            while line[-2:] == '=\n':
331                line = line[:-2]
332                newline = ifile.readline()
333                if newline[:len(QUOTE)] == QUOTE:
334                    newline = newline[len(QUOTE):]
335                line = line + newline
336            line = mime_decode(line)
337        message.append(line)
338        if not has_iso_chars:
339            if iso_char.search(line):
340                has_iso_chars = must_quote_body = 1
341        if not must_quote_body:
342            if len(line) > MAXLEN:
343                must_quote_body = 1
344
345    # convert and output header and body
346    for line in header:
347        if must_quote_header:
348            line = mime_encode_header(line)
349        chrset_res = chrset.match(line)
350        if chrset_res:
351            if has_iso_chars:
352                # change us-ascii into iso-8859-1
353                if chrset_res.group(2).lower() == 'us-ascii':
354                    line = '%s%s%s' % (chrset_res.group(1),
355                                       CHARSET,
356                                       chrset_res.group(3))
357            else:
358                # change iso-8859-* into us-ascii
359                line = '%sus-ascii%s' % chrset_res.group(1, 3)
360        if has_cte and cte.match(line):
361            line = 'Content-Transfer-Encoding: '
362            if is_base64:
363                line = line + 'base64\n'
364            elif must_quote_body:
365                line = line + 'quoted-printable\n'
366            else:
367                line = line + '7bit\n'
368        ofile.write(line)
369    if (must_quote_header or must_quote_body) and not is_mime:
370        ofile.write('Mime-Version: 1.0\n')
371        ofile.write('Content-Type: text/plain; ')
372        if has_iso_chars:
373            ofile.write('charset="%s"\n' % CHARSET)
374        else:
375            ofile.write('charset="us-ascii"\n')
376    if must_quote_body and not has_cte:
377        ofile.write('Content-Transfer-Encoding: quoted-printable\n')
378    ofile.write(header_end)
379
380    for line in message:
381        if must_quote_body:
382            line = mime_encode(line, 0)
383        ofile.write(line)
384    ofile.write(message_end)
385
386    line = message_end
387    while multipart:
388        if line == multipart + '--\n':
389            # read bit after the end of the last part
390            while 1:
391                line = ifile.readline()
392                if not line:
393                    return
394                if must_quote_body:
395                    line = mime_encode(line, 0)
396                ofile.write(line)
397        if line == multipart + '\n':
398            nifile = File(ifile, multipart)
399            mimify_part(nifile, ofile, 1)
400            line = nifile.peek
401            if not line:
402                # premature end of file
403                break
404            ofile.write(line)
405            continue
406        # unexpectedly no multipart separator--copy rest of file
407        while 1:
408            line = ifile.readline()
409            if not line:
410                return
411            if must_quote_body:
412                line = mime_encode(line, 0)
413            ofile.write(line)
414
415def mimify(infile, outfile):
416    """Convert 8bit parts of a MIME mail message to quoted-printable."""
417    if type(infile) == type(''):
418        ifile = open(infile)
419        if type(outfile) == type('') and infile == outfile:
420            import os
421            d, f = os.path.split(infile)
422            os.rename(infile, os.path.join(d, ',' + f))
423    else:
424        ifile = infile
425    if type(outfile) == type(''):
426        ofile = open(outfile, 'w')
427    else:
428        ofile = outfile
429    nifile = File(ifile, None)
430    mimify_part(nifile, ofile, 0)
431    ofile.flush()
432
433import sys
434if __name__ == '__main__' or (len(sys.argv) > 0 and sys.argv[0] == 'mimify'):
435    import getopt
436    usage = 'Usage: mimify [-l len] -[ed] [infile [outfile]]'
437
438    decode_base64 = 0
439    opts, args = getopt.getopt(sys.argv[1:], 'l:edb')
440    if len(args) not in (0, 1, 2):
441        print usage
442        sys.exit(1)
443    if (('-e', '') in opts) == (('-d', '') in opts) or \
444       ((('-b', '') in opts) and (('-d', '') not in opts)):
445        print usage
446        sys.exit(1)
447    for o, a in opts:
448        if o == '-e':
449            encode = mimify
450        elif o == '-d':
451            encode = unmimify
452        elif o == '-l':
453            try:
454                MAXLEN = int(a)
455            except (ValueError, OverflowError):
456                print usage
457                sys.exit(1)
458        elif o == '-b':
459            decode_base64 = 1
460    if len(args) == 0:
461        encode_args = (sys.stdin, sys.stdout)
462    elif len(args) == 1:
463        encode_args = (args[0], sys.stdout)
464    else:
465        encode_args = (args[0], args[1])
466    if decode_base64:
467        encode_args = encode_args + (decode_base64,)
468    encode(*encode_args)