[comp.sys.ibm.pc] UUENCODE for MS-DOS - Source for Pascal version, newly modified

lane@dalcs.UUCP (John Wright/Dr. Pat Lane) (07/23/88)

In article <7521@cup.portal.com>, mslater@cup.portal.com writes:
> Can someone point me to a version of UUENCODE that runs under MS-DOS?
> MKS toolkit does not include it.

Hi There.  Funny you should mention that.  I recently decided I could loosen
some of my downloading log-jam if I started doing my UUDECODEing, etc. on the
PC end (download first..think later, you know).  So I downloaded a C version
and two Pascal versions that had been posted to the net in times past.  The
second Pascal pgm was a minor mod to the first (see "Attempted revision
history" in the comments of the Pascal programs).

Neither had documentation; the posters pointed to regular UNIX manuals for the 
C version and only appologized for the Pascal version.  I found that several 
minor changes were required to get the Pascal version to compile.  Then I 
discovered that the operation and results of the Pascal and C pgms differed.
(The C version works as advertised; I have written comment/documentation the
the Pascal program).  The C version was 3 times faster but I found the 
Pascal version more "user friendly" (no doubt a matter of taste). 

Anyway, I made some mods of my own to the Pascal programs (again, see comments)
and now present to the world just what it didn't need, yet another version of
UUENCODE/UUDECODE.  BTW, I'm not that experienced in Pascal (and ran out of
time as if I had any in the first place :-) so there are a few hitches left 
(in both C and Pascal vers) to solve...see comments and be my guest.

I include here the Pascal sources of my modified version.  If you (or anyone
wan't binaries, I can mail them).  I also have the C version and the 
previous two versions of the Pascal programs I could mail if requested.
I did't include them as they have been posted many times before.

Enjoy.

PS: In the same spirit I downloaded a pgm called UNSHAR supposed to extract
"shar" archives on MSDOS.  It starts to work but then hangs every time.
Anybody gotten this to work?


Following are the sources of my Pascal version of UUENCODE and UUDECODE.  

---------------------UUENCODE.PAS---------------------------------------
Program uuencode;

{ Program UUENCODE  -  Encodes a file to printable ascii characters so that it
                       can be sent over mail networks, etc.  A companion pgm
                       UUDECODE decodes the file and restores the original.
}

{ Usage:  uuencode input-file [mode] [output-file]

  Mode is distinguised by a leading digit and defaults to 644.  If given,
  it should be 3 octal digits representing the permission modes to be given
  the file when it is restored.  Each digit has bits read, write, execute and
  the three digits are for owner, group and world.  Thus "644" would give the
  owner read and write but only read for anyone else.  For MSDOS, all digits
  should be the same, either 6 for normal files or 4 for read-only.  Note that
  the companion UUDECODE program for this program ignorres mode.

  The default for the output-file is the input-file name with ext. ".UUE".
  The default extension for an explicitly specified output-file is ".UUE".

  While running the program lists the input and output file names, the size
  in bytes on the input file, a running count of bytes read from input and
  the percentage of the file processed (updated as each output line is
  written).  Upon finishing the output file size is printed.

  If no arguments are given the program printes a short "usage" message.
}

{ The input-file name and mode are stored in the output file in a header
  line with the format "begin <mode> <file name>".  These are used by
  UUDECODE to restore the file.  The mode is ignorred for MSDOS systems;
  On UNIX systems it is the file's permission mode.  The header is followed
  by lines of encoded information where the first char is a coded line length
  (the number of original data bytes plus 32 to make it a printing char) and
  the rest of the chars are coded data; every 3 data bytes become 4 ascii chars
  in the range of decimal 33 ("!") to 96 ("`").  All lines but the last should
  be 61 chars long and begin with "M", signifying 45 data bytes.  The last data
  line is followed by a line containing "`" (or a blank) in col 1, signifying 0
  data chars and then a line containing "end".  See the UNIX Reference Manual 
  for details.

  Note that in some versions, blanks (decimal 32 in ascii) are output in place
  of "`" chars.  UUDECODE treats them as equivalent.

  The coded file is typically 35% larger than the original.
}

{ UUENCODE and UUDECODE originated on UNIX systems and were written in C.
  This program was written in Pascal specifically for MSDOS.

  The C programs are also avaliable on MSDOS and there are some differences.
  The usage differs in that the C version is a "filter".  By default it takes
  its input from the standard input and always outputs to the standard output.
  Thus, there is no default for the output file name (and no default exten.).

  In the C version, the file name for the header is explicitly specified as 
  a non-optional command line parameter while in this program it is taken from
  the input file name which is a non-optional command line parameter.  In the
  C version the mode is derived from the file using the "fstat" function and
  cannot be over-ridden.  In this program it will be "644" unless specified in
  the command line.  The C version of UUDECODE properly handles the mode while
  the Pascal version ignorres it.

  The C version provides no progress display for the user as the standard output 
  is used for the data output.

  Finally, note that some C versions do not translate blanks in the coded data
  lines to "`".  All versions of UUDECODE appear to treat them as equivalent.
}

{ Known bugs:
  Appears to open the input file in write mode (read-only files fail).
  Program does not read and encode file read-only attribute (not implemented).
  Bytes counts over 32768 go negative in user report.
}

{ Attempted revision history.
  The origins of the UNIX C version are unknown to me but one version I've
  seen has the SCCS id: "@(#)uuencode.c 5.1 (Berkeley) 7/2/83"
  The C version for MSDOS that I have was adapated (or at least commented) 
  by Don Kneller (kneller@ucsfcgl.UUCP) and posted to USENET by Tom Reingold
  (reintom@rocky2.UUCP) on 25/May/86.
  The original version of this program was also contained in Tom's posting.
  Nobody seems to know the author.  Slightly modified version were posted by
  Pierre Darmon (darmon@polaris) of IBM's T.J.Watson Labs on 30/Oct/86.  The
  orginal pgms wrote periods to the screen as processing progressed.  Pierre's
  version gave the input file size, a running byte count and percentage left
  to process.
  On 17/July/88, John Wright (lane@dalcs) made the following changes:
   - Added the line "Uses Crt;" and replaced "read (kbd, ch);" with
     "ch := ReadKey;" to make it compile.
   - The progress display gives the byte count and percentage processed and the
     size of the output file.
   - The default extension for the output file (when it is given of the
     command line) was dropped.  the ".UUE" extension is still used with the
     input file name when the output file name is not specified.
   - These comments were written.
   - the "Usage" message was added. 
   - Other minor coding improvements.
}

uses Crt;

  CONST header = 'begin';
        trailer = 'end';
        defaultMode = '644';
        defaultExtension = '.uue';
        offset = 32;
        charsPerLine = 60;
        bytesPerHunk = 3;
        sixBitMask = $3F;

  TYPE string80 = string[80];

  VAR infile: file of byte;
      outfile: text;
      infilename, outfilename, mode: string80;
      lineLength, numbytes, bytesInLine: integer;
      line: array [0..59] of char;
      hunk: array [0..2] of byte;
      chars: array [0..3] of byte;
      size,total :integer;

  procedure Usage;

    begin {usage}
      writeln;
      writeln('Usage:  uuencode input-file [mode] [output-file]');
      writeln;
      writeln('Mode is distinguised by a leading digit and default is 644.');
      writeln('Default output-file is the input-file name with ext. ".UUE".');
{      writeln('Extension on output-file name defaults to ".UUE"');}
      writeln;
      halt
    end; {usage}

  procedure Abort (message: string80);

    begin {abort}
      writeln(message);
      close(infile);
      close(outfile);
      halt
    end; {abort}

  procedure Init;

    procedure GetFiles;

      VAR i: integer;
          temp: string80;
          ch: char;

      begin {GetFiles}
        if ParamCount < 1 then Usage;
        infilename := ParamStr(1);
        {$I-}
        assign (infile, infilename);
        reset (infile);
        {$i+}
        if IOResult > 0 then abort (concat ('Can''t open file ', infilename));
        writeln;
        writeln('Uuencoding file:  ', infilename);

        i := pos('.', infilename);
        if i = 0
          then outfilename := infilename
          else outfilename := copy (infilename, 1, pred(i));
        outfilename := concat(outfilename, defaultExtension);

        mode := defaultMode;

        if ParamCount > 1 then
          for i := 2 to ParamCount do
            begin
              temp := Paramstr(i);
              if temp[1] in ['0'..'9']
                then mode := temp
                else begin
                  outfilename := temp {;
                  if pos ('.', outfilename) = 0 then
                    outfilename := concat(outfilename, defaultExtension)}
                end;
            end;
        assign (outfile, outfilename);
        writeln('Output to file:   ', outfilename);

        {$i-}
        reset(outfile);
        {$i+}
        if IOresult = 0 then
          begin
            Write ('Overwrite current ', outfilename, '? [Y/N] ');
            repeat
              ch := ReadKey; { read (kbd, ch);}
              ch := Upcase(ch)
            until ch in ['Y', 'N'];
            writeln (ch);
            if ch = 'N' then abort(concat (outfilename, ' not overwritten.'))
          end;
{        close(outfile); {this line was in original pgm but made pgm bomb}

        {$i-}
        rewrite(outfile);
        {$i+}
        if ioresult > 0 then abort(concat('Can''t open ', outfilename));

        total:=FileSize(infile);
        if total < 0 then total:=total+65536;
        writeln('Input file size:  ',total:7,' bytes');

      end; {getfiles}

    begin {Init}
      GetFiles;
      bytesInLine := 0;
      lineLength := 0;
      numbytes := 0;
      size:=0;
      writeln (outfile, header, ' ', mode, ' ', infilename);
    end; {init}

  procedure FlushLine;

    VAR i: integer;

    procedure writeout(ch: char);

      begin {writeout}
        if ch = ' ' then write(outfile, '`')
                    else write(outfile, ch)
      end; {writeout}

    procedure DispSize;

      begin {DispSize}
        write('Bytes processed:  ',size:7,' (',
          100.0*size/total:3:0,'%)',chr(13));
      end; {DispSize}

    begin {FlushLine}
      {write ('.');}
      DispSize;
      writeout(chr(bytesInLine + offset));
      for i := 0 to pred(lineLength) do
        writeout(line[i]);
      writeln (outfile);
      lineLength := 0;
      bytesInLine := 0
    end; {FlushLine}

  procedure FlushHunk;

    VAR i: integer;

    begin {FlushHunk}
      if lineLength = charsPerLine then FlushLine;
      chars[0] := hunk[0] shr 2;
      chars[1] := (hunk[0] shl 4) + (hunk[1] shr 4);
      chars[2] := (hunk[1] shl 2) + (hunk[2] shr 6);
      chars[3] := hunk[2] and sixBitMask;
      for i := 0 to 3 do
        begin
          line[lineLength] := chr((chars[i] and sixBitMask) + offset);
          lineLength := succ(lineLength)
        end;
      bytesInLine := bytesInLine + numbytes;
      numbytes := 0
    end; {FlushHunk}

  procedure Encode1;

    begin {encode1};
      if numbytes = bytesperhunk then flushhunk;
      read (infile, hunk[numbytes]);
      size:=size+1;
      numbytes := succ(numbytes)
    end; {encode1}

  procedure Terminate;

    function GetFileSize (filename: string80): integer;

    VAR fi: file of byte;
        size: integer;

    begin {GetFileSize}
      assign(fi,filename); reset(fi);
      size:=FileSize(fi); close(fi);
      if size < 0 then size:=size+65536;
      GetFileSize := size
    end; {GetFileSize}

    begin {Terminate}
      if numbytes > 0 then flushhunk;
      if lineLength > 0 then flushline;
      flushline;
      writeln (outfile, trailer);
      close (outfile);
      close (infile);
      size:=GetFileSize(outfilename);
      writeln;
      writeln('Output file size: ',size:7,' bytes');
    end; {Terminate}


  begin {uuencode}
    Init;
    while not eof (infile) do Encode1;
    Terminate;
  end. {uuencode}

---------------------UUDECODE.PAS---------------------------------------
program uudecode;

{ Program UUDECODE  -  Decodes a file of printable ascii characters created by
                       companion pgm UUENCODE, restoring the original file.
}

{ Usage:  uudecode input-file

  The name for the restored file is taken from the header line in the input
  file.  The mode on the header line is ignorred and the file is restored as
  read/write.

  Not applicable: The default extension for the input-file is ".UUE".

  While running, the program lists the input and output file names, the size
  in bytes on the input file, a running count of bytes read from input and
  the percentage of the file processed (updated as each input line is read).
  Upon finishing the output file size is printed.

  If no arguments are given the program printes a short "usage" message.
}

{ The program reads the input file looking for a header line with "begin" in
  col 1-6 followed by the permission mode and file name for the restored file.
  See commants in the UUENCODE program for a desciption of the mode.  On UNIX
  systems it is the file's permission mode bits as three octal digits.  This
  program ignorres it.  The header is followed by lines of encoded information
  where the first char is a coded line length (the number of original data bytes
  plus 32 to make it a printing char) and the rest of the chars are coded data;
  every 3 data bytes become 4 ascii chars in the range of decimal 33 ("!") to
  96 ("`").  All lines but the last should be 61 chars long and begin with "M",
  signifying 45 data bytes. The last data line is followed by a line containing
  only "`" (or a blank) in col 1, signifying 0 data chars and then a line 
  containing "end".  See the UNIX Reference Manual for details.

  Note that in some versions of UUENCODE, blanks (decimal 32 in ascii) are 
  output in place of "`" chars.  This program treats them as equivalent.

  The coded file is typically 35% larger than the original.
}

{ UUENCODE and UUDECODE originated on UNIX systems and were written in C.
  This program was written in Pascal specifically for MSDOS.

  The C programs are also avaliable on MSDOS and there are some differences.
  The usage differs in that the C version is a "filter".  By default, it takes
  its input from the standard input.  Both pgms use the name on the header for
  the output file name.  There is no default extension for the input file name.

  The C version provides no progress display for the user.

  The C version of UUDECODE calls "chmod" to set the attribute bits of the file
  based on mode (read as an octal number).  It appears to set the read-only bit
  if and only if the 2nd bit of the first octal digit is 0.

  Finally, note that some C versions do not translate blanks in the coded data
  lines to "`".  All versions of UUDECODE appear to treat them as equivalent.
}

{ Known bugs:
  Appears to open the input file in write mode (read-only files fail).
  Program does not restore file read-only attribute (not implemented).
  Bytes counts over 32768 go negative in user report.
}

{ Attempted revision history.
  The origins of the UNIX C version are unknown to me but the program that was
  adapted for MSDOS and posted (see below) has the SCCS id:
  "@(#)uudecode.c 5.1 (Berkeley) 7/2/83"
  The C version for MSDOS that I have was adapated (or at least commented) 
  by Don Kneller (kneller@ucsfcgl.UUCP) and posted to USENET by Tom Reingold
  (reintom@rocky2.UUCP) on 25/May/86.
  The original version of this program was also contained in Tom's posting.
  Nobody seems to know the author.  Slightly modified version were posted by
  Pierre Darmon (darmon@polaris) of IBM's T.J.Watson Labs on 30/Oct/86.  The
  orginal pgms wrote periods to the screen as processing progressed.  Pierre's
  version gave the input file size, a running byte count and percentage left
  to process.
  On 17/July/88, John Wright (lane@dalcs) made the following changes:
   - Added the line "Uses Crt;" and replaced "read (kbd, ch);" with
     "ch := ReadKey;" to make it compile.
   - The progress display gives the byte count and percentage processed and the
     size of the output file.
   - The default extension for the input file was dropped.
   - These comments were written.
   - the "Usage" message was added. 
   - Other minor coding improvements.
}

uses Crt;

  CONST {defaultSuffix = '.uue';}
        offset = 32;

  TYPE string80 = string[80];

  VAR infilename: string80;
      outfilename: string80;
      infile: text;
      outfile: file of byte;
      line: string80;
      lineNum: integer;
      size,total :integer;

  procedure Abort(message: string80);

    begin {abort}
      writeln;
      if lineNum > 0 then write('Line ', lineNum, ': ');
      writeln(message);
      halt
    end; {Abort}

  procedure Usage;

    begin {usage}
      writeln;
      writeln('Usage:  uudecode input-file');
      writeln;
{      writeln('Default extension for the input file name is ".UUE".');}
      writeln('Output file name is taken from header ("begin") line.');
      writeln('Mode from the header line is ignorred.  Lines before');
      writeln('the "begin" line or after the "end" line are ignorred.');
      writeln;
      halt
    end; {usage}

  procedure NextLine;

    begin {NextLine}
      LineNum := succ(LineNum);
      readln(infile, line);
      size := size+length(line)+2;  {+2 is for CR/LF}
    end; {NextLine}

  procedure Init;

    function GetFileSize (filename: string80): integer;

    VAR fi: file of byte;
        size: integer;

    begin {GetFileSize}
      assign(fi,filename); reset(fi);
      size:=FileSize(fi); close(fi);
      if size < 0 then size:=size+65536;
      GetFileSize := size
    end; {GetFileSize}

    procedure GetInFile;

      begin {GetInFile}
        infilename := ParamStr(1);
{        if pos('.', infilename) = 0
          then infilename := concat(infilename, defaultSuffix);}
        assign(infile, infilename);
        {$i-}
        reset(infile);
        {$i+}
        if IOresult > 0 then abort (concat('Can''t open ', infilename));
        writeln;
        writeln ('Uudecoding file:  ', infilename);
      end; {GetInFile}

    procedure GetOutFile;

      var header, mode: string80;
          ch: char;

      procedure ParseHeader;

        VAR index: integer;

        Procedure NextWord(var word:string80; var index: integer);

          begin {nextword}
            word := '';
            while header[index] = ' ' do
              begin
                index := succ(index);
                if index > length(header) then abort ('Incomplete header')
              end;
            while header[index] <> ' ' do
              begin
                word := concat(word, header[index]);
                index := succ(index)
              end
          end; {NextWord}

        begin {ParseHeader}
          header := concat(header, ' ');
          index := 7;
          NextWord(mode, index);
          NextWord(outfilename, index)
        end; {ParseHeader}

      begin {GetOutFile}
        if eof(infile) then abort('Nothing to decode.');
        NextLine;
        while not ((copy(line, 1, 6) = 'begin ') or eof(infile)) do
          NextLine;
        if eof(infile) then abort('Nothing to decode.');
        header := line;
        ParseHeader;
        assign(outfile, outfilename);
        writeln ('Output to file:   ', outfilename);
        {$i-}
        reset(outfile);
        {$i+}
        if IOresult = 0 then
          begin
            write ('Overwrite current ', outfilename, '? [Y/N] ');
            repeat
              ch := ReadKey; { read (kbd, ch);}
              ch := UpCase(ch)
            until ch in ['Y', 'N'];
            write(ch);
            if ch = 'N' then abort ('Overwrite cancelled.');
            writeln
          end;
        rewrite (outfile);
      end; {GetOutFile}

    begin {init}
      if ParamCount = 0 then Usage;
      lineNum := 0;
      GetInFile;
      size:=0;
      GetOutFile;
      total:=GetFileSize(infilename);
      writeln('Input file size:  ',total:7,' bytes');
    end; { init}


  procedure DecodeLine;

    VAR lineIndex, byteNum, count, i: integer;
        chars: array [0..3] of byte;
        hunk: array [0..2] of byte;

    function nextch: char;

      begin {nextch}
        lineIndex := succ(lineIndex);
        if lineIndex > length(line) then abort('Line too short.');
        if not (line[lineindex] in [' '..'`'])
          then abort('Illegal character in line.');
        if line[lineindex] = '`' then nextch := ' '
                                 else nextch := line[lineIndex]
      end; {nextch}

    procedure DecodeByte;

      procedure GetNextHunk;

        VAR i: integer;

        begin {GetNextHunk}
          for i := 0 to 3 do chars[i] := ord(nextch) - offset;
          hunk[0] := (chars[0] shl 2) + (chars[1] shr 4);
          hunk[1] := (chars[1] shl 4) + (chars[2] shr 2);
          hunk[2] := (chars[2] shl 6) + chars[3];
          byteNum := 0
        end; {GetNextHunk}

      begin {DecodeByte}
        if byteNum = 3 then GetNextHunk;
        write (outfile, hunk[byteNum]);
        {writeln(bytenum, ' ', hunk[byteNum]);}
        byteNum := succ(byteNum)
      end; {DecodeByte}

    begin {DecodeLine}
      lineIndex := 0;
      byteNum := 3;
      count := (ord(nextch) - offset);
      for i := 1 to count do DecodeByte
    end; {DecodeLine}

  function CheckLine: boolean;

    begin {CheckLine}
      if line = '' then abort ('Blank line in file');
      CheckLine := not (line[1] in [' ', '`'])
    end; {CheckLine}

  procedure DispSize;

    begin {NextSize}
      write('Bytes processed:  ',size:7,' (',
            100.0*size/total:3:0,'%)',chr(13));
    end; {DispSize}

  procedure Terminate;

    var trailer: string80;

    begin {Terminate}
      if eof(infile) then abort ('Abnormal end.');
      NextLine;
      DispSize;
      if length (line) < 3 then abort ('Abnormal end.');
      if copy (line, 1, 3) <> 'end' then abort ('Abnormal end.');
      writeln;
      close (infile);
      size:=FileSize(outfile);
      if size < 0 then size:=size+65536;
      writeln('Output file size: ',size:7,' bytes');
      close (outfile)
    end; {Terminate}

  begin {uudecode}
    Init;
    NextLine;
    while CheckLine do
      begin
        DecodeLine;
        NextLine;
        DispSize
      end;
    Terminate
  end.
---------------------------------------------------------------


-- 
John Wright      //////////////////      Phone:  902-424-3805  or  902-424-6527
Post: c/o Dr Pat Lane, Biology Dept, Dalhousie U, Halifax N.S., CANADA  B3H-4H8 
Cdn/Bitnet: lane@cs.dal.cdn    Arpa: lane%dalcs.uucp@uunet.uu.net
Uucp: lane@dalcs.uucp or {uunet,watmath,utai,garfield}!dalcs!lane  

jimmy@ncr-sd.SanDiego.NCR.COM (Jim Mealhouse) (07/26/88)

In article <2960@dalcs.UUCP> lane@dalcs.UUCP (John Wright/Dr. Pat Lane) writes:
>Following are the sources of my Pascal version of UUENCODE and UUDECODE.  
>---------------------UUENCODE.PAS---------------------------------------
>---------------------UUDECODE.PAS---------------------------------------
>{ Known bugs:
>  Appears to open the input file in write mode (read-only files fail).
>  Program does not read and encode file read-only attribute (not implemented).
>  Bytes counts over 32768 go negative in user report.
>-- 
>John Wright      //////////////////      Phone:  902-424-3805  or  902-424-6527
>Post: c/o Dr Pat Lane, Biology Dept, Dalhousie U, Halifax N.S., CANADA  B3H-4H8 
>Cdn/Bitnet: lane@cs.dal.cdn    Arpa: lane%dalcs.uucp@uunet.uu.net
>Uucp: lane@dalcs.uucp or {uunet,watmath,utai,garfield}!dalcs!lane  


There is a fix for the byte counts over 32768. In Turbo Pascal 3.0 there
are some new functions - LongFileSize,LongFilePosition,LongSeek-.
They use Reals. So change the routines that use FileSize to use the new
functions. Remember to use reals.

page 199 in TP 3.0 manual.


[================]
James Mealhouse
UseNet<wherever>!<sdcsvax,ihnp4>!ncr-sd!jimmy
jimmy@ncr-sd.SanDiego.NCR.COM
[================]