############################################################################ # # File: iidecode.icn # # Subject: Program to decode text in style of uudecode # # Author: Richard L. Goerwitz, enhanced by Frank J. Lhota # # Date: May 2, 2001 # ########################################################################### # # This file is in the public domain. # ############################################################################ # # Version: 2.0 # ########################################################################### # # This is an Icon port of the UNIX/C uudecode utility. Since # uudecode is publicly distributable BSD code, I simply grabbed a # copy, and rewrote it in Icon. The only basic functional changes I # made to the program were: (1) To simplify the notion of file mode # (everything is encoded with 0644 permissions), and (2) to add a # command-line switch for xxencoded files (similar to uuencoded # files, but capable of passing unscathed through non-ASCII EBCDIC # sites). # # usage: iidecode [infile] [-x] # # Usage is compatible with that of the UNIX uudecode command, i.e. a # first (optional) argument gives the name the file to be decoded. # If this is omitted, iidecode just uses the standard input. The -x # switch (peculiar to iidecode) forces use of the the xxdecoding # algorithm. If you try to decode an xxencoded file without speci- # -x on the command line, iidecode will try to forge ahead anyway. # If it thinks you've made a mistake, iidecode will inform you after # the decode is finished. # # # FIXES: Speeded up substantially (more than twice as fast on my # machine) by using a more icon-ish algorithm. We decode in two # steps: # # 1) The coded characters are mapped to "small bytes", # each with 2 zero high bits, i.e. <<= "\x3F". # 2) We then 'pack' the small bytes by taking groups of 4 small bytes # (each with 2 zero high bits and 6 data bits) and packing # the data bits into groups of 3 bytes. # # There are numerous advantages to this approach. The icon map # function is much faster than the 'C'-ish alternatives. We can # process things one line at a time. Also, the different decoding # mechanisms (old BSD, new BSD, xxdecode) can be produces by simply # using different map parameters. # ############################################################################ # # See also: iiencode.icn # ############################################################################ link options global oversizes procedure main ( a ) local opt, in, out, dest, is_xx initial oversizes := 0 opt := options ( a, "-x" ) is_xx := opt [ "x" ] # Check for correct number of args. case *a of { 0 : in := &input 1 : in := open ( a [ 1 ], "r" ) | { write ( &errout, "Can't open input file, ", a [ 1 ], ".\n_ usage: iidecode [infile] [-x]" ) exit ( 1 ) } default : { write ( &errout, "usage: iidecode [infile] [-x]" ) exit ( 2 ) } } # Find the "begin" line, and determine the destination file name. !in ? { ="begin " & tab ( many ( &digits ) ) & # mode ignored tab ( many ( ' ' ) ) & dest := tab ( 0 ) } # If dest is null, the begin line either isn't present, or is # corrupt (which necessitates our aborting with an error msg.). if /dest then { write ( &errout, "No begin line." ) exit ( 3 ) } # Tilde expansion is heavily UNIX dependent, and we can't always # safely write the file to the current directory. Our only choice # is to abort. if match ( "~", dest ) then { write ( &errout, "Please remove ~ from input file begin line." ) exit ( 4 ) } out := open ( dest, "wu" ) decode ( in, out, is_xx ) # decode checks for "end" line if not match ( "end", !in ) then { write ( &errout, "No end line.\n" ) exit ( 5 ) } # Check global variable oversizes (set by decode) # to see if we used the correct decoding algorithm. if oversizes > 0 then { if \is_xx then { write ( &errout, "Input file appears to have been uuencoded.\n_ Try invoking iidecode without the -x arg." ) } else { write ( &errout, "Input file is either corrupt, or xxencoded.\n_ Please check the output; try the -x option." ) } } every close ( ( &input ~=== in ) | out ) exit ( 0 ) end ########################################################################### # # Reads encoded lines from file in, decodes them, # and writes the decoded data# to out. # "uu" decoding is done unless \is_xx, in which case "xx" decoding is done. # ########################################################################### procedure decode(in, out, is_xx) # Copy from in to out, decoding as you go along. local line, n, coded, unpacked, badchars if \is_xx then { coded := "_ +-0123456789ABCD_ EFGHIJKLMNOPQRST_ UVWXYZabcdefghij_ klmnopqrstuvwxyz" unpacked := "_ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F_ \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F_ \x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F_ \x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F" } else { # # To be safe, we map both " " and "`" to "\x00" # coded := " _ !\"#$%&'()*+,-./_ 0123456789:;<=>?_ @ABCDEFGHIJKLMNO_ PQRSTUVWXYZ[\\]^_`" unpacked := "_ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F_ \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F_ \x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F_ \x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F\x00" } badchars := ~ coded while line := read ( in ) do { if *line = 0 then { write ( &errout, "Short file.\n" ) exit ( 10 ) } line ? while tab ( upto ( badchars ) + 1 ) do oversizes +:= 1 map ( line, coded, unpacked ) ? { n := ord ( move ( 1 ) ) line := tab ( 0 ) if not ( *line % 4 = 0, n <= ( ( *line / 4 ) * 3 ) ) then { write ( &errout, "Short and/or corrupt line:\n", line ) if /is_xx & oversizes > 0 then write ( &errout, "Try -x option?" ) exit ( 15 ) } # Uuencode signals the end of the coded text by a space # and a line (i.e. a zero-length line, coded as a space). if n <= 0 then break writes ( out, left ( repack ( line ), n ) ) } } return end ########################################################################### # # Takes groups of 4 bytes in s (each byte should have 2 zero high bits) # and packs the 6 lower data bits into group of 3 bytes. # ########################################################################### procedure repack ( s ) local n, grp s ? { s := "" while grp := move ( 4 ) do { n := 0 grp ? while n := ord ( move ( 1 ) ) % 16r40 + ( n * 16r40 ) s ||:= char ( ishift ( iand ( n, 16rFF0000 ), -16 ) ) || char ( ishift ( iand ( n, 16r00FF00 ), - 8 ) ) || char ( iand ( n, 16r0000FF ) ) } } return s end