uu.py 6.74 KB
Newer Older
1
#! /usr/bin/env python3
2

3 4 5
# Copyright 1994 by Lance Ellinghouse
# Cathedral City, California Republic, United States of America.
#                        All Rights Reserved
6 7
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
8
# provided that the above copyright notice appear in all copies and that
9
# both that copyright notice and this permission notice appear in
10
# supporting documentation, and that the name of Lance Ellinghouse
11
# not be used in advertising or publicity pertaining to distribution
12 13 14 15 16 17 18 19
# of the software without specific, written prior permission.
# LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE
# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 21 22 23 24
#
# Modified by Jack Jansen, CWI, July 1995:
# - Use binascii module to do the actual line-by-line conversion
#   between ascii and binary. This results in a 1000-fold speedup. The C
#   version is still 5 times faster, though.
25
# - Arguments more compliant with python standard
26

27 28
"""Implementation of the UUencode and UUdecode functions.

29 30
encode(in_file, out_file [,name, mode], *, backtick=False)
decode(in_file [, out_file, mode, quiet])
31
"""
32

33
import binascii
34
import os
35
import sys
36

37 38
__all__ = ["Error", "encode", "decode"]

39 40
class Error(Exception):
    pass
41

42
def encode(in_file, out_file, name=None, mode=None, *, backtick=False):
43 44 45 46
    """Uuencode file"""
    #
    # If in_file is a pathname open it and change defaults
    #
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
    opened_files = []
    try:
        if in_file == '-':
            in_file = sys.stdin.buffer
        elif isinstance(in_file, str):
            if name is None:
                name = os.path.basename(in_file)
            if mode is None:
                try:
                    mode = os.stat(in_file).st_mode
                except AttributeError:
                    pass
            in_file = open(in_file, 'rb')
            opened_files.append(in_file)
        #
        # Open out_file if it is a pathname
        #
        if out_file == '-':
            out_file = sys.stdout.buffer
        elif isinstance(out_file, str):
            out_file = open(out_file, 'wb')
            opened_files.append(out_file)
        #
        # Set defaults for name and mode
        #
72
        if name is None:
73
            name = '-'
74
        if mode is None:
75 76 77 78 79
            mode = 0o666
        #
        # Write the data
        #
        out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii"))
80
        data = in_file.read(45)
81
        while len(data) > 0:
82
            out_file.write(binascii.b2a_uu(data, backtick=backtick))
83
            data = in_file.read(45)
84 85 86 87
        if backtick:
            out_file.write(b'`\nend\n')
        else:
            out_file.write(b' \nend\n')
88 89 90
    finally:
        for f in opened_files:
            f.close()
91 92


93
def decode(in_file, out_file=None, mode=None, quiet=False):
94 95 96 97
    """Decode uuencoded file"""
    #
    # Open the input file, if needed.
    #
98
    opened_files = []
99
    if in_file == '-':
100
        in_file = sys.stdin.buffer
101
    elif isinstance(in_file, str):
102
        in_file = open(in_file, 'rb')
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
        opened_files.append(in_file)

    try:
        #
        # Read until a begin is encountered or we've exhausted the file
        #
        while True:
            hdr = in_file.readline()
            if not hdr:
                raise Error('No valid begin line found in input file')
            if not hdr.startswith(b'begin'):
                continue
            hdrfields = hdr.split(b' ', 2)
            if len(hdrfields) == 3 and hdrfields[0] == b'begin':
                try:
                    int(hdrfields[1], 8)
                    break
                except ValueError:
                    pass
        if out_file is None:
            # If the filename isn't ASCII, what's up with that?!?
            out_file = hdrfields[2].rstrip(b' \t\r\n\f').decode("ascii")
            if os.path.exists(out_file):
                raise Error('Cannot overwrite existing file: %s' % out_file)
        if mode is None:
            mode = int(hdrfields[1], 8)
        #
        # Open the output file
        #
        if out_file == '-':
            out_file = sys.stdout.buffer
        elif isinstance(out_file, str):
            fp = open(out_file, 'wb')
136
            try:
137 138
                os.path.chmod(out_file, mode)
            except AttributeError:
139
                pass
140 141 142 143 144
            out_file = fp
            opened_files.append(out_file)
        #
        # Main decoding loop
        #
145
        s = in_file.readline()
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        while s and s.strip(b' \t\r\n\f') != b'end':
            try:
                data = binascii.a2b_uu(s)
            except binascii.Error as v:
                # Workaround for broken uuencoders by /Fredrik Lundh
                nbytes = (((s[0]-32) & 63) * 4 + 5) // 3
                data = binascii.a2b_uu(s[:nbytes])
                if not quiet:
                    sys.stderr.write("Warning: %s\n" % v)
            out_file.write(data)
            s = in_file.readline()
        if not s:
            raise Error('Truncated input file')
    finally:
        for f in opened_files:
            f.close()
162 163

def test():
164 165
    """uuencode/uudecode main program"""

166 167 168 169
    import optparse
    parser = optparse.OptionParser(usage='usage: %prog [-d] [-t] [input [output]]')
    parser.add_option('-d', '--decode', dest='decode', help='Decode (instead of encode)?', default=False, action='store_true')
    parser.add_option('-t', '--text', dest='text', help='data is text, encoded format unix-compatible text?', default=False, action='store_true')
170

171 172
    (options, args) = parser.parse_args()
    if len(args) > 2:
173
        parser.error('incorrect number of arguments')
174
        sys.exit(1)
175

176 177 178
    # Use the binary streams underlying stdin/stdout
    input = sys.stdin.buffer
    output = sys.stdout.buffer
179
    if len(args) > 0:
180
        input = args[0]
181
    if len(args) > 1:
182
        output = args[1]
183

184 185
    if options.decode:
        if options.text:
186
            if isinstance(output, str):
187
                output = open(output, 'wb')
188
            else:
189
                print(sys.argv[0], ': cannot do -t to stdout')
190 191
                sys.exit(1)
        decode(input, output)
192
    else:
193
        if options.text:
194
            if isinstance(input, str):
195
                input = open(input, 'rb')
196
            else:
197
                print(sys.argv[0], ': cannot do -t from stdin')
198 199
                sys.exit(1)
        encode(input, output)
200 201 202

if __name__ == '__main__':
    test()