home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / rexxuuxx.zip / UUXXCODE.PLI < prev    next >
Text File  |  1997-05-13  |  41KB  |  875 lines

  1. %PROCESS DLLINIT;
  2.  /*********************************************************************/
  3.  /* This program is freeware, distributed as is, without any warranty */
  4.  /* of its usefulness for any purpose. You may use it freely. You may */
  5.  /* also redistribute it, provided no charge is levied beyond the     */
  6.  /* price of its distribution medium. However, the author retains all */
  7.  /* intellectual property rights.                                     */
  8.  /*                                                                   */
  9.  /*                                                                   */
  10.  /*  Copyright (C) David W. Noon, 1995, 1997                          */
  11.  /*                                                                   */
  12.  /*********************************************************************/
  13.  
  14.  /* Dynamic link Library to encode and decode messages */
  15.  /* using UU, XX or BASE64 coding.                     */
  16.  /*                                                    */
  17.  /* Author: David W. Noon                              */
  18.  /*         January 1995                               */
  19.  
  20.  (NOOFL,NOUFL,NOFOFL,NOZDIV):
  21.  UUXXCODE:
  22.  PACKAGE OPTIONS(REENTRANT REORDER)
  23.           EXPORTS(UUDECODE_FILE,UUENCODE_FILE,XXDECODE_FILE,
  24.           XXENCODE_FILE,BASE64_DECODE,BASE64_ENCODE);
  25.  
  26.           /* Global constants */
  27.      DCL  UU_default_xlate_table        CHAR(64) VALUE(
  28.   '`!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'),
  29.           XX_default_xlate_table        CHAR(64) VALUE(
  30.   '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'),
  31.           Base64_xlate_table            CHAR(64) VALUE(
  32.   'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
  33.  
  34.  UUDECODE_FILE:
  35.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  36.  
  37.      DCL  Parm_ptr                      PTR;
  38.  
  39.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  40.                2    In_len                   BIN FIXED(16,0) UNSIGNED,
  41.                2    Horton_bug               BIT(1) ALIGNED,
  42.                2    Msg_file                 CHAR(260) VAR,
  43.                2    Input_filename           CHAR((260) REFER (In_len)) VAR;
  44.  
  45.      CALL UUXXDECODE_FILE(Input_filename,UU_default_xlate_table,
  46.                Horton_bug,Msg_file);
  47.      RETURN;
  48.  END UUDECODE_FILE;
  49.  
  50.  XXDECODE_FILE:
  51.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  52.      DCL  Parm_ptr                      PTR;
  53.  
  54.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  55.                2    In_len                   BIN FIXED(16,0) UNSIGNED,
  56.                2    Horton_bug               BIT(1) ALIGNED,
  57.                2    Msg_file                 CHAR(260) VAR,
  58.                2    Input_filename           CHAR((260) REFER (In_len)) VAR;
  59.  
  60.      CALL UUXXDECODE_FILE(Input_filename,XX_default_xlate_table,
  61.                Horton_bug,Msg_file);
  62.      RETURN;
  63.  END XXDECODE_FILE;
  64.  
  65.  UUENCODE_FILE:
  66.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  67.      DCL  Parm_ptr                      PTR;
  68.  
  69.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  70.                2    In_cnt                   BIN FIXED(16,0) UNSIGNED,
  71.                2    Output_filename          CHAR(260) VAR,
  72.                2    Msg_file                 CHAR(260) VAR,
  73.                2    Input_filename((1) REFER (In_cnt)) CHAR(260) VAR;
  74.  
  75.      CALL UUXXENCODE_FILE(Input_filename,Output_filename,
  76.                UU_default_xlate_table);
  77.      RETURN;
  78.  END UUENCODE_FILE;
  79.  
  80.  XXENCODE_FILE:
  81.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  82.      DCL  Parm_ptr                      PTR;
  83.  
  84.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  85.                2    In_cnt                   BIN FIXED(16,0) UNSIGNED,
  86.                2    Output_filename          CHAR(260) VAR,
  87.                2    Msg_file                 CHAR(260) VAR,
  88.                2    Input_filename((1) REFER (In_cnt)) CHAR(260) VAR;
  89.  
  90.      CALL UUXXENCODE_FILE(Input_filename,Output_filename,
  91.                XX_default_xlate_table);
  92.      RETURN;
  93.  END XXENCODE_FILE;
  94.  %PAGE;
  95.  /****************************************************/
  96.  /*                                                  */
  97.  /* Subroutine to decode UUENCODEd or XXENCODEd      */
  98.  /* messages, as per Unix.                           */
  99.  /*                                                  */
  100.  /****************************************************/
  101.  UUXXDECODE_FILE:
  102.  PROC(Input_filename_list,Default_xlate_table,Horton_bug,Msg_file);
  103.      DCL  Input_filename_list           CHAR(*) NONASGN,
  104.           Default_xlate_table           CHAR(64) NONASGN,
  105.           Horton_bug                    BIT(1) ALIGNED NONASGN,
  106.           Msg_file                      CHAR(*) VAR NONASGN;
  107.  
  108.      DCL  (Bytes_written,Check_sum,Byte_count,File_count,i,p,Scan_cnt)
  109.                                         BIN FIXED(31,0) UNSIGNED,
  110.           Slack_bytes                   BIN FIXED(31,0) SIGNED,
  111.           Biased_byte                   BIN FIXED(8,0) UNSIGNED,
  112.           Filename_array(*)             CHAR(*) VAR CTL,
  113.           Output_filename               CHAR(256) VAR INIT(''),
  114.           Table_area                    CHAR(65)
  115.                                         INIT((Default_xlate_table||' ')),
  116.           Xlate_table                   CHAR(64) DEF Table_area POS(1),
  117.           Unbiased_ptr                  PTR,
  118.           1    Unbiased_bits            UNION BASED(Unbiased_ptr),
  119.                2    Unbiased_bytes           CHAR(4),
  120.                2    Byte_value(4)            BIN FIXED(8,0) UNSIGNED,
  121.                2    Bit_fields,
  122.                     3    *                        BIT(2),
  123.                     3    Unbiased_bits_1          BIT(6),
  124.                     3    *                        BIT(2),
  125.                     3    Unbiased_bits_1A         BIT(2),
  126.                     3    Unbiased_bits_2          BIT(4),
  127.                     3    *                        BIT(2),
  128.                     3    Unbiased_bits_2A         BIT(4),
  129.                     3    Unbiased_bits_3          BIT(2),
  130.                     3    *                        BIT(2),
  131.                     3    Unbiased_bits_3A         BIT(6),
  132.           (Suppress_checksums,Trailing_blanks_added,Prefix_read) BIT(1),
  133.           Input_record                  CHAR(512) VAR,
  134.           Output_rec_ptr                PTR,
  135.           1    Output_rec_area          BASED(Output_rec_ptr),
  136.                2    Output_rec_len           BIN FIXED(31,0) UNSIGNED,
  137.                2    Output_rec((Byte_count) REFER (Output_rec_len))
  138.                                              BIN FIXED(8,0) UNSIGNED,
  139.  
  140.           (ADDR,COLLATE,COPY,DIVIDE,FILEWRITE,HBOUND,IAND,ISLL,ISRL,LBOUND,
  141.                     LEFT,LENGTH,MOD,RANK,REM,RIGHT,SEARCH,SUBSTR,TRANSLATE,
  142.                     TRIM,UNSPEC,VERIFY) BUILTIN,
  143.  
  144.           Input_file                    FILE RECORD INPUT INT
  145.                                         ENV(CONSECUTIVE),
  146.           Output_file                   FILE RECORD OUTPUT INT
  147.                                         ENV(CONSECUTIVE RECSIZE(1)),
  148.           SYSPRINT                      PRINT INT;
  149.  
  150.      OPEN FILE(SYSPRINT) PAGESIZE(55) TITLE('/'||Msg_file);
  151.  
  152.      CALL Split_concatenated_files(Input_filename_list,Filename_array);
  153.  
  154.      /* We haven't read a UU/XX prefix yet. */
  155.      Prefix_read = '0'B;
  156.  
  157.      PUT FILE(SYSPRINT) EDIT
  158.      ('Records dropped from file(s) ',Input_filename_list,':')
  159.      (A);
  160.  
  161.      /* We come back here after each encoded file in the
  162.         input stream has been decoded. */
  163.      DO File_count = LBOUND(Filename_array,1) TO HBOUND(Filename_array,1);
  164.           /* Ignore any file that cannot be found */
  165.           ON UNDF(Input_file)
  166.           BEGIN;
  167.                PUT FILE(SYSPRINT) EDIT
  168.                ('Unable to open file ',Filename_array(File_count))
  169.                (A);
  170.                GO TO Skip_file;
  171.           END;
  172.           OPEN FILE(Input_file) TITLE('/'||Filename_array(File_count)||
  173.                      ',TYPE(TEXT),RECSIZE(512)');
  174.           ON ENDFILE(Input_file)
  175.                GO TO End_of_input;
  176.  
  177.           /* We come back here after we have completely decoded an output
  178.                output file. We keep reading the input file, in case it
  179.                contains more than one output file. */
  180.      Read_input_file:
  181.           DO LOOP;
  182.                IF ¬ Prefix_read THEN
  183.                DO;
  184.                     /* Look for the 'begin 666 filename.xxx' prefix */
  185.                     DO UNTIL(Prefix_read);
  186.                          READ FILE(Input_file) INTO(Input_record);
  187.                          /* Get rid of trailing blanks */
  188.                          Input_record = TRIM(Input_record,'',' ');
  189.                          SELECT;
  190.                               WHEN(Input_record = 'table')
  191.                               DO;
  192.                                    /* We have a custom tramslation table! */
  193.                                    READ FILE(Input_file) INTO(Input_record);
  194.                                    SUBSTR(Xlate_table,1,32) = Input_record;
  195.                                    READ FILE(Input_file) INTO(Input_record);
  196.                                    SUBSTR(Xlate_table,33,32) = Input_record;
  197.                               END;
  198.  
  199.                               WHEN(LENGTH(Input_record) > 7 &
  200.                                         LEFT(Input_record,6) = 'begin ')
  201.                               DO;
  202.                                    /* Bingo! We have a begin */
  203.                                    p = VERIFY(Input_record,' ',7);
  204.                                    IF p ¬= 0 THEN
  205.                                    DO;
  206.                                         /* Skip over mode */
  207.                                         p = SEARCH(Input_record,' ',p);
  208.                                         IF p ¬= 0 THEN
  209.                                         DO;
  210.                                              /* Find filename */
  211.                                              p = VERIFY(Input_record,' ',p);
  212.                                              IF p ¬= 0 THEN
  213.                                              DO;
  214.                                                   Output_filename =
  215.                                                        SUBSTR(Input_record,p);
  216.                                                   Prefix_read = '1'B;
  217.                                              END;
  218.                                         END;
  219.                                    END;
  220.                               END;
  221.                               OTHERWISE
  222.                                    PUT FILE(SYSPRINT) SKIP EDIT
  223.                                    (Input_record)
  224.                                    (A);
  225.                          END;
  226.                     END;
  227.  
  228.                     /* Initialise counter */
  229.                     Bytes_written = 0;
  230.                     /* Allow checksums, by default */
  231.                     Suppress_checksums = '0'B;
  232.  
  233.                     PUT FILE(SYSPRINT) EDIT
  234.                     ('Decoding file ',Output_filename,' using table:',
  235.                          Xlate_table)
  236.                     (SKIP,3 A);
  237.                     /* Open the output file in the current directory */
  238.                     OPEN FILE(Output_file) TITLE('/'||Output_filename||
  239.                               ',TYPE(U),EA(N),APPEND(N)');
  240.                END;
  241.  
  242.           Decode_loop:
  243.                DO LOOP;
  244.                     DO UNTIL(LENGTH(Input_record) ¬= 0 &
  245.                               LEFT(Input_record,1) ¬= LEFT(Xlate_table,1));
  246.                          READ FILE(Input_file) INTO(Input_record);
  247.                          /* Get rid of trailing blanks */
  248.                          Input_record = TRIM(Input_record,'',' ');
  249.                          IF Input_record = 'end' THEN
  250.                               LEAVE Decode_loop;
  251.                     END;
  252.  
  253.                     Byte_count = SEARCH(Xlate_table,LEFT(Input_record,1));
  254.                     IF Byte_count > 1 &
  255.                               VERIFY(Input_record,Table_area,2) = 0 THEN
  256.                Process_record:
  257.                     DO;
  258.                          Byte_count -= 1;
  259.                          Scan_cnt = DIVIDE(Byte_count*4+2,3,16,0);
  260.                          Slack_bytes = LENGTH(Input_record) - Scan_cnt;
  261.  
  262.                          SELECT;
  263.                               WHEN(Slack_bytes < 1)
  264.                               DO;  /* Pad record with trailing "blanks". */
  265.                                    IF Suppress_checksums THEN
  266.                                    DO;
  267.                                         p = 1 - Slack_bytes;
  268.                                         Slack_bytes = 1;
  269.                                    END;
  270.                                    ELSE
  271.                                    DO;
  272.                                         p = 2 - Slack_bytes;
  273.                                         Slack_bytes = 2;
  274.                                    END;
  275.                                    Input_record ||= COPY(LEFT(Xlate_table,1),p);
  276.                                    Trailing_blanks_added = '1'B;
  277.                               END;
  278.  
  279.                               WHEN(Slack_bytes > 2 + MOD(-Scan_cnt,4))
  280.                               DO;  /* Record is too long */
  281.                                    PUT FILE(SYSPRINT) SKIP EDIT
  282.                                    (Input_record,'Slack_bytes = ',Slack_bytes)
  283.                                    (A,SKIP,A,F(5));
  284.                                    LEAVE Process_record; /* Drop the record */
  285.                               END;
  286.  
  287.                               OTHERWISE
  288.                                    Trailing_blanks_added = '0'B;
  289.                          END;
  290.  
  291.                          /* Take care of blanks as lowest value bytes */
  292.                          IF LEFT(Xlate_table,1) ¬= ' ' THEN
  293.                               Input_record = TRANSLATE(Input_record,
  294.                                    LEFT(Xlate_table,1),' ');
  295.  
  296.                          IF ¬ Suppress_checksums & ¬ Horton_bug THEN
  297.                               IF Slack_bytes = 2 THEN
  298.                               DO;
  299.                                    Check_sum = 0;
  300.                                    DO i = 2 TO LENGTH(Input_record) - 1;
  301.                                         Check_sum +=
  302.                                                  RANK(SUBSTR(Input_record,i,1));
  303.                                    END;
  304.                                    Biased_byte = SEARCH(Xlate_table,
  305.                                              RIGHT(Input_record,1)) - 1;
  306.                                    IF REM(Check_sum,64) ¬= Biased_byte &
  307.                                              RIGHT(Input_record,1) ¬= 'M' &
  308.                                    RIGHT(Input_record,1) ¬= LEFT(Xlate_table,1)
  309.                                              THEN
  310.                                    DO;
  311.                                         IF Trailing_blanks_added THEN
  312.                                              Suppress_checksums = '1'B;
  313.                                         ELSE
  314.                                              PUT FILE(SYSPRINT) SKIP EDIT
  315.                                              (Input_record,'Calc. checksum =',
  316.                                                   Check_sum,
  317.                                                   'Checksum byte =',Biased_byte)
  318.                                              (A,SKIP,2(A,F(6)));
  319.                                    END;
  320.                               END;
  321.                               ELSE
  322.                                    Suppress_checksums = '1'B;
  323.  
  324.                          /* Allocate an output work area of sufficient length */
  325.                          ALLOCATE Output_rec_area SET(Output_rec_ptr);
  326.                          Output_rec_len = 0;
  327.  
  328.                          /* Remove the bias on all the bytes */
  329.                          SUBSTR(Input_record,2) =
  330.                                    TRANSLATE(SUBSTR(Input_record,2),
  331.                                    LEFT(COLLATE,LENGTH(Xlate_table)),
  332.                                    Xlate_table);
  333.  
  334.                          /* Decode 4-byte chunks into 3-byte chunks */
  335.                          DO Unbiased_ptr = ADDR(Input_record) + 3 BY 4
  336.                                    TO ADDR(Input_record) + (Scan_cnt - 1);
  337.                               Output_rec(Output_rec_len+1) =
  338.                                         ISLL(Byte_value(1),2) +
  339.                                         ISRL(Byte_value(2),4);
  340.                               Output_rec(Output_rec_len+2) =
  341.                                         ISLL(Byte_value(2),4) +
  342.                                         ISRL(Byte_value(3),2);
  343.                               Output_rec(Output_rec_len+3) =
  344.                                         ISLL(Byte_value(3),6) +
  345.                                         Byte_value(4);
  346.                               Output_rec_len += 3;
  347.                          END;
  348.                          /* See if we have any stragglers left */
  349.                          i = REM(Byte_count,3);
  350.                          IF i ¬= 0 THEN
  351.                          DO;  /* Unbiased_ptr should already point there */
  352.                               Output_rec(Output_rec_len+1) =
  353.                                         ISLL(Byte_value(1),2) +
  354.                                         ISRL(Byte_value(2),4);
  355.                               Output_rec_len += i;
  356.                               IF i = 2 THEN
  357.                                    Output_rec(Output_rec_len) =
  358.                                              ISLL(Byte_value(2),4) +
  359.                                              ISRL(Byte_value(3),2);
  360.                          END;
  361.  
  362.                          IF Output_rec_len ¬= Byte_count THEN
  363.                          DO;
  364.                               PUT FILE(SYSPRINT) SKIP EDIT
  365.                               (Input_record,'Byte_count =',Byte_count,
  366.                                    'Output_rec_len =',Output_rec_len)
  367.                               (A,SKIP,2(A,F(4)));
  368.                               Output_rec_len = Byte_count;
  369.                               FREE Output_rec_area;
  370.                               LEAVE Process_record; /* Drop the record */
  371.                          END;
  372.  
  373.                          /* See if we need to correct Horton's error */
  374.                          IF ¬ Suppress_checksums & Horton_bug THEN
  375.                               IF Slack_bytes = 2 THEN
  376.                               DO;
  377.                                    Check_sum = 0;
  378.                                    DO i = 1 TO Output_rec_len;
  379.                                         Check_sum += Output_rec(i);
  380.                                    END;
  381.                                    p = Scan_cnt + 2;
  382.                                    /* Determine how many bytes of "float" */
  383.                                    SELECT(LENGTH(Input_record) - p);
  384.                                         WHEN(1)
  385.                                         DO; /* 1 byte "float" */
  386.                                              Check_sum +=
  387.                                                  RANK(SUBSTR(Input_record,p,1));
  388.                                         END;
  389.  
  390.                                         WHEN(2)
  391.                                         DO; /* 2 byte "float" */
  392.                                              Unbiased_ptr = ADDR(Input_record) +
  393.                                                   (p + 1);
  394.                                              Check_sum +=
  395.                                                        ISLL(Byte_value(1),2) +
  396.                                                        ISRL(Byte_value(2),4);
  397.                                         END;
  398.  
  399.                                         WHEN(3)
  400.                                         DO; /* 3 byte "float" */
  401.                                              Unbiased_ptr = ADDR(Input_record) +
  402.                                                   (p + 1);
  403.                                              Check_sum +=
  404.                                                        ISLL(Byte_value(1),2) +
  405.                                                        ISRL(Byte_value(2),4) +
  406.                                                        ISLL(Byte_value(2),4) +
  407.                                                        ISRL(Byte_value(3),2);
  408.                                         END;
  409.  
  410.                                         OTHERWISE
  411.                                              /* Let it die */;
  412.                                    END;
  413.                                    Biased_byte = SEARCH(Xlate_table,
  414.                                              RIGHT(Input_record,1)) - 1;
  415.                                    IF REM(Check_sum,64) ¬= Biased_byte THEN
  416.                                    DO;
  417.                                         IF Trailing_blanks_added THEN
  418.                                              Suppress_checksums = '1'B;
  419.                                         ELSE
  420.                                              PUT FILE(SYSPRINT) SKIP EDIT
  421.                                              (Input_record,'Calc. checksum =',
  422.                                                   Check_sum,
  423.                                                   'Checksum byte =',Biased_byte)
  424.                                              (A,SKIP,2(A,F(6)));
  425.                                    END;
  426.                               END;
  427.                               ELSE
  428.                                    Suppress_checksums = '1'B;
  429.  
  430.                          /* Write this run of bytes to the decoded file */
  431.                          Bytes_written += FILEWRITE(Output_file,
  432.                                    ADDR(Output_rec),Output_rec_len);
  433.                          /* No memory leaks here */
  434.                          FREE Output_rec_area;
  435.                     END;
  436.                     ELSE
  437.                          PUT FILE(SYSPRINT) SKIP EDIT
  438.                          (Input_record)
  439.                          (A);
  440.                END Decode_loop;
  441.  
  442.                PUT FILE(SYSPRINT) SKIP(2) EDIT
  443.                ('File ',Output_filename,' successfully decoded. Bytes written:',
  444.                     Bytes_written)
  445.                (3 A,F(9));
  446.  
  447.                CLOSE FILE(Output_file);
  448.                /* We need another UU/XX prefix now. */
  449.                Prefix_read = '0'B;
  450.           END Read_input_file;
  451.  
  452.           /* This one's done. Try the next input file. */
  453.      End_of_input:
  454.           CLOSE FILE(Input_file);
  455.      Skip_file:
  456.      END;
  457.  
  458.      IF Prefix_read THEN
  459.      DO;  /* We never hit the 'end' record after a 'begin' was found */
  460.           PUT FILE(SYSPRINT) SKIP(2) EDIT
  461.           ("'end' record not found! Encoded file is probably corrupt.")
  462.           (A)
  463.           ('File ',Output_filename,' NOT successfully decoded. Bytes written:',
  464.                Bytes_written)
  465.           (SKIP,3 A,F(9));
  466.  
  467.           CLOSE FILE(Output_file);
  468.      END;
  469.  
  470.      CLOSE FILE(SYSPRINT);
  471.  
  472.      /* Again, no memory leaks */
  473.      FREE Filename_array;
  474.  
  475.      RETURN;
  476.  END UUXXDECODE_FILE;
  477.  %PAGE;
  478.  /****************************************************/
  479.  /*                                                  */
  480.  /* Subroutine to encode UUENCODEd or XXENCODEd      */
  481.  /* messages, as per Unix.                           */
  482.  /*                                                  */
  483.  /****************************************************/
  484.  UUXXENCODE_FILE:
  485.  PROC(Input_filename,Output_filename,Xlate_table);
  486.      DCL  Input_filename(*)             CHAR(*) VAR NONASGN,
  487.           Output_filename               CHAR(*) VAR NONASGN,
  488.           Xlate_table                   CHAR(64) NONASGN;
  489.  
  490.      DCL  (Byte_count,Check_sum,Rec_len,p) BIN FIXED(31,0) UNSIGNED,
  491.           j                             BIN FIXED(31,0) SIGNED,
  492.           Output_byte                   BIN FIXED(8,0) UNSIGNED,
  493.           Table_record                  CHAR(5) STATIC NONASGN
  494.                                         INIT('table'),
  495.           End_record                    CHAR(3) STATIC NONASGN
  496.                                         INIT('end'),
  497.           Output_record                 CHAR(62) VAR,
  498.           Decoded_bytes                 BIT(45*8) ALIGNED,
  499.  
  500.           (ADDR,COPY,EDIT,FILEREAD,HBOUND,ISLL,LBOUND,LEFT,LENGTH,PLIFILL,
  501.                     RANK,REM,SEARCHR,STG,SUBSTR,TRIM,UNSPEC) BUILTIN,
  502.  
  503.           Input_file                    FILE RECORD INPUT INT
  504.                                         ENV(CONSECUTIVE RECSIZE(1)),
  505.           Output_file                   FILE RECORD OUTPUT INT
  506.                                         ENV(CONSECUTIVE RECSIZE(64));
  507.  
  508.      OPEN FILE(Output_file) TITLE('/'||Output_filename||
  509.                ',TYPE(TEXT),EA(N),APPEND(N)');
  510.  
  511.      /* Write the current translation table as a custom table */
  512.      WRITE FILE(Output_file) FROM(Table_record);
  513.      Output_record = LEFT(Xlate_table,32);
  514.      WRITE FILE(Output_file) FROM(Output_record);
  515.      Output_record = SUBSTR(Xlate_table,33,32);
  516.      WRITE FILE(Output_file) FROM(Output_record);
  517.  
  518.      /* Loop through all files requested */
  519.      DO j = LBOUND(Input_filename) TO HBOUND(Input_filename);
  520.           /* Ignore any file that cannot be found */
  521.           ON UNDF(Input_file)
  522.                GO TO Skip_file;
  523.           OPEN FILE(Input_file) TITLE('/'||Input_filename(j)||',TYPE(U)');
  524.  
  525.           /* Write an empty line, then a 'begin' prefix record */
  526.           Output_record = '';
  527.           WRITE FILE(Output_file) FROM(Output_record);
  528.           /* Scrap any drive/path info. in filename */
  529.           p = SEARCHR(Input_filename(j),'/:\');
  530.           Output_record = 'begin 666 ' || SUBSTR(Input_filename(j),p+1);
  531.           WRITE FILE(Output_file) FROM(Output_record);
  532.  
  533.           Byte_count = 0;
  534.           DO LOOP;
  535.                /* Make the output work area all low values */
  536.                CALL PLIFILL(ADDR(Decoded_bytes),'00'X,STG(Decoded_bytes));
  537.                /* Read an input work area */
  538.                Rec_len = FILEREAD(Input_file,ADDR(Decoded_bytes),
  539.                          STG(Decoded_bytes));
  540.                Byte_count += Rec_len;
  541.                /* A short record means that's all that's left */
  542.                IF Rec_len < STG(Decoded_bytes) THEN
  543.                     LEAVE;
  544.                /* Encode the data into the output work area
  545.                   First, the default length */
  546.                Output_record = SUBSTR(Xlate_table,46,1);
  547.                /* Now encode each group of 6 bits as a byte */
  548.                DO p = 1 TO LENGTH(Decoded_bytes) - 5 BY 6;
  549.                     UNSPEC(Output_byte) = '00'B || SUBSTR(Decoded_bytes,p,6);
  550.                     Output_record ||= SUBSTR(Xlate_table,Output_byte+1,1);
  551.                END;
  552.                /* Calculate the checksum */
  553.                Check_sum = 0;
  554.                DO p = 2 UPTHRU 61;
  555.                     Check_sum += RANK(SUBSTR(Output_record,p,1));
  556.                END;
  557.                /* Append the checksum. for those who care */
  558.                Output_record ||= SUBSTR(Xlate_table,REM(Check_sum,64)+1,1);
  559.                /* Write the output work area to the output file */
  560.                WRITE FILE(Output_file) FROM(Output_record);
  561.           END;
  562.  
  563.           CLOSE FILE(Input_file);
  564.  
  565.           /* In case the last record had some bytes in it ... */
  566.           IF Rec_len > 0 THEN
  567.           DO;
  568.                /* Encode the data into the output work area
  569.                   First, the default length */
  570.                Output_record = SUBSTR(Xlate_table,Rec_len+1,1);
  571.                /* Now encode each group of 6 bits as a byte */
  572.                DO p = 1 TO ISLL(Rec_len,3) BY 6;
  573.                     UNSPEC(Output_byte) = '00'B || SUBSTR(Decoded_bytes,p,6);
  574.                     Output_record ||= SUBSTR(Xlate_table,Output_byte+1,1);
  575.                END;
  576.                /* Calculate the checksum */
  577.                Check_sum = 0;
  578.                DO p = 2 UPTHRU LENGTH(Output_record);
  579.                     Check_sum += RANK(SUBSTR(Output_record,p,1));
  580.                END;
  581.                /* Append the checksum. for those who care */
  582.                Output_record ||= SUBSTR(Xlate_table,REM(Check_sum,64)+1,1);
  583.                /* Write the output work area to the output file */
  584.                WRITE FILE(Output_file) FROM(Output_record);
  585.           END;
  586.           /* Write a zero-length encoded record */
  587.           Output_record = COPY(LEFT(Xlate_table,1),2);
  588.           WRITE FILE(Output_file) FROM(Output_record);
  589.           /* Now an 'end' record */
  590.           WRITE FILE(Output_file) FROM(End_record);
  591.           /* Now write the size the decoder should expect */
  592.           Output_record = 'size ' || TRIM(EDIT(Byte_count,'(8)Z9'));
  593.           WRITE FILE(Output_file) FROM(Output_record);
  594.      Skip_file:
  595.      END;
  596.  
  597.      CLOSE FILE(Output_file);
  598.  
  599.      RETURN;
  600.  END UUXXENCODE_FILE;
  601.  %PAGE;
  602.  /****************************************************/
  603.  /*                                                  */
  604.  /* Subroutine to decode BASE64 messages as per Unix */
  605.  /*                                                  */
  606.  /****************************************************/
  607.  BASE64_DECODE:
  608.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  609.      DCL  Parm_ptr                      PTR;
  610.  
  611.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  612.                2    Valid_rec_len            BIN FIXED(16,0) UNSIGNED,
  613.                2    In_len                   BIN FIXED(16,0) UNSIGNED,
  614.                2    Output_filename          CHAR(260) VAR,
  615.                2    Msg_file                 CHAR(260) VAR,
  616.                2    Input_filename_list      CHAR((260) REFER (In_len)) VAR;
  617.  
  618.      DCL  (File_count,j,Trail)          BIN FIXED(31,0) UNSIGNED,
  619.           Bytes_written                 BIN FIXED(31,0) UNSIGNED
  620.                                         INIT(0),
  621.  
  622.           Filename_array(*)             CHAR(*) VAR CTL,
  623.           Input_record                  CHAR(Valid_rec_len) VAR,
  624.           Encoded_ptr                   PTR,
  625.           1    Encoded_bits             UNION BASED(Encoded_ptr),
  626.                2    Encoded_bytes            CHAR(4),
  627.                2    Byte_value(4)            BIN FIXED(8,0) UNSIGNED,
  628.                2    Bit_fields,
  629.                     3    *                        BIT(2),
  630.                     3    Bit_field_1              BIT(6),
  631.                     3    *                        BIT(2),
  632.                     3    Bit_field_1A             BIT(2),
  633.                     3    Bit_field_2              BIT(4),
  634.                     3    *                        BIT(2),
  635.                     3    Bit_field_2A             BIT(4),
  636.                     3    Bit_field_3              BIT(2),
  637.                     3    *                        BIT(2),
  638.                     3    Bit_field_3A             BIT(6),
  639.           Decoded_byte(0:(Valid_rec_len*3)/4-1) BIN FIXED(8,0) UNSIGNED,
  640.           Equal_sign_found              BIT(1) INIT('0'B),
  641.           Recsize_text                  CHAR(26),
  642.  
  643.           (ADDR,COLLATE,FILEWRITE,HBOUND,IAND,ISLL,ISRL,LBOUND,LEFT,
  644.                     LENGTH,REM,RIGHT,TRANSLATE,TRIM,VERIFY) BUILTIN,
  645.  
  646.           Input_file                    FILE RECORD INPUT INT
  647.                                         ENV(CONSECUTIVE),
  648.           Output_file                   FILE RECORD OUTPUT INT
  649.                                         ENV(CONSECUTIVE RECSIZE(1)),
  650.           SYSPRINT                      PRINT INT;
  651.  
  652.      OPEN FILE(SYSPRINT) PAGESIZE(55) TITLE('/'||Msg_file),
  653.           FILE(Output_file) TITLE('/'||Output_filename||
  654.                ',TYPE(U),EA(N),APPEND(N)');
  655.  
  656.      /* Format the record size for later use */
  657.      PUT STRING(Recsize_text) EDIT
  658.      (',TYPE(TEXT),RECSIZE(',Valid_rec_len,')')
  659.      (A,P'(5)9',A);
  660.  
  661.      CALL Split_concatenated_files(Input_filename_list,Filename_array);
  662.  
  663.      PUT FILE(SYSPRINT) EDIT
  664.      ('Records dropped from file(s) ',Input_filename_list,':')
  665.      (A);
  666.  
  667.      DO File_count = LBOUND(Filename_array,1) TO HBOUND(Filename_array,1)
  668.                UNTIL(Equal_sign_found);
  669.           /* Ignore any file that cannot be found */
  670.           ON UNDF(Input_file)
  671.           BEGIN;
  672.                PUT FILE(SYSPRINT) EDIT
  673.                ('Unable to open file ',Filename_array(File_count))
  674.                (A);
  675.                GO TO Skip_file;
  676.           END;
  677.           OPEN FILE(Input_file) TITLE('/'||Filename_array(File_count)||
  678.                      Recsize_text);
  679.           ON ENDFILE(Input_file)
  680.                GO TO End_of_file;
  681.           /* Ignore any over-length records */
  682.           ON RECORD(Input_file)
  683.                Input_record = '';
  684.  
  685.           DO UNTIL(Equal_sign_found);
  686.                DO UNTIL(LENGTH(Input_record) > 0);
  687.                     READ FILE(Input_file) INTO(Input_record);
  688.                END;
  689.  
  690.                IF RIGHT(Input_record,1) = '=' THEN
  691.                DO;
  692.                     Input_record = TRIM(Input_record,'','=');
  693.                     Equal_sign_found =
  694.                               VERIFY(Input_record,Base64_xlate_table) = 0;
  695.                END;
  696.  
  697.                /* Determine no. of trailing bytes. Should be 0, 2 or 3 */
  698.                Trail = REM(LENGTH(Input_record),4);
  699.                IF Equal_sign_found | (Trail ¬= 1 &
  700.                          VERIFY(Input_record,Base64_xlate_table) = 0) THEN
  701.                DO;
  702.                     Input_record = TRANSLATE(Input_record,LEFT(COLLATE,64),
  703.                               Base64_xlate_table);
  704.                     j = 0;
  705.                     /* Translate each chunk of 4 bytes into 3 decoded bytes */
  706.                     DO Encoded_ptr = ADDR(Input_record) + 2 BY 4
  707.                               TO ADDR(Input_record) +
  708.                               (LENGTH(Input_record) - Trail - 2);
  709.                          Decoded_byte(j) = ISLL(Byte_value(1),2) +
  710.                                    ISRL(Byte_value(2),4);
  711.                          Decoded_byte(j+1) = ISLL(Byte_value(2),4) +
  712.                                    ISRL(Byte_value(3),2);
  713.                          Decoded_byte(j+2) = ISLL(Byte_value(3),6) +
  714.                                    Byte_value(4);
  715.                          j += 3;
  716.                     END;
  717.                     /* Now take care of any trailing bytes */
  718.                     IF Trail > 0 THEN
  719.                     DO;
  720.                          Decoded_byte(j) = ISLL(Byte_value(1),2) +
  721.                                    ISRL(Byte_value(2),4);
  722.                          j += 1;
  723.                          IF Trail = 3 THEN
  724.                          DO;
  725.                               Decoded_byte(j) = ISLL(Byte_value(2),4) +
  726.                                         ISRL(Byte_value(3),2);
  727.                               j += 1;
  728.                          END;
  729.                     END;
  730.                     /* Write the decoded bytes to the output file */
  731.                     Bytes_written +=
  732.                               FILEWRITE(Output_file,ADDR(Decoded_byte),j);
  733.                END;
  734.                ELSE
  735.                     PUT FILE(SYSPRINT) EDIT
  736.                     (Input_record)
  737.                     (SKIP,A);
  738.           END; /* DO UNTIL(Equal_sign_found) */
  739.      End_of_file:
  740.           CLOSE FILE(Input_file);
  741.      Skip_file:
  742.      END; /* DO File_count = */
  743.  
  744.      IF Equal_sign_found THEN
  745.           PUT FILE(SYSPRINT) EDIT
  746.           ('File ',Output_filename,' successfully decoded. Bytes written:',
  747.                     Bytes_written)
  748.           (SKIP,3 A,F(9));
  749.      ELSE
  750.           PUT FILE(SYSPRINT) EDIT
  751.           ('Trailing equal sign not found! Encoded file is probably corrupt.')
  752.           (SKIP,A)
  753.           ('File ',Output_filename,' NOT successfully decoded. Bytes written:',
  754.                     Bytes_written)
  755.           (SKIP,3 A,F(9));
  756.  
  757.      CLOSE FILE(Output_file),
  758.           FILE(SYSPRINT);
  759.  
  760.      FREE Filename_array;
  761.  
  762.      RETURN;
  763.  END BASE64_DECODE;
  764.  %PAGE;
  765.  /****************************************************/
  766.  /*                                                  */
  767.  /* Subroutine to encode BASE64 messages as per Unix */
  768.  /*                                                  */
  769.  /****************************************************/
  770.  BASE64_ENCODE:
  771.  PROC(Parm_ptr) OPTIONS(BYVALUE NODESCRIPTOR);
  772.      DCL  Parm_ptr                      PTR;
  773.  
  774.      DCL  1    Parm_struct              BASED(Parm_ptr) NONASGN,
  775.                2    Input_filename           CHAR(260) VAR,
  776.                2    Output_filename          CHAR(260) VAR;
  777.  
  778.      DCL  (Rec_len,p)                   BIN FIXED(31,0) UNSIGNED,
  779.           Output_byte                   BIN FIXED(8,0) UNSIGNED,
  780.           Output_record                 CHAR(60) VAR,
  781.           Decoded_bytes                 BIT(45*8) ALIGNED,
  782.  
  783.           (ADDR,FILEREAD,ISLL,LENGTH,MAXLENGTH,PLIFILL,STG,SUBSTR,UNSPEC)
  784.                                         BUILTIN,
  785.  
  786.           Input_file                    FILE RECORD INPUT INT
  787.                                         ENV(CONSECUTIVE RECSIZE(1)),
  788.           Output_file                   FILE RECORD OUTPUT INT
  789.                                         ENV(CONSECUTIVE RECSIZE(62));
  790.  
  791.      /* Ignore any file that cannot be found */
  792.      ON UNDF(Input_file)
  793.           GO TO Skip_file;
  794.      OPEN FILE(Input_file) TITLE('/'||Input_filename||',TYPE(U)');
  795.  
  796.      OPEN FILE(Output_file) TITLE('/'||Output_filename||
  797.                ',TYPE(TEXT),EA(N),APPEND(N)');
  798.  
  799.      DO LOOP;
  800.           Output_record = '';
  801.           /* Make the output work area all low values */
  802.           CALL PLIFILL(ADDR(Decoded_bytes),'00'X,STG(Decoded_bytes));
  803.           /* Read an input work area */
  804.           Rec_len = FILEREAD(Input_file,ADDR(Decoded_bytes),STG(Decoded_bytes));
  805.           /* A short record means that's all that's left */
  806.           IF Rec_len < STG(Decoded_bytes) THEN
  807.                LEAVE;
  808.           /* Now encode each group of 6 bits as a byte */
  809.           DO p = 1 TO LENGTH(Decoded_bytes) - 5 BY 6;
  810.                UNSPEC(Output_byte) = '00'B || SUBSTR(Decoded_bytes,p,6);
  811.                Output_record ||= SUBSTR(Base64_xlate_table,Output_byte+1,1);
  812.           END;
  813.           /* Write the output work area to the output file */
  814.           WRITE FILE(Output_file) FROM(Output_record);
  815.      END;
  816.  
  817.      IF Rec_len > 0 THEN
  818.      DO;
  819.           /* Encode each group of 6 bits as a byte */
  820.           DO p = 1 TO ISLL(Rec_len,3) BY 6;
  821.                UNSPEC(Output_byte) = '00'B || SUBSTR(Decoded_bytes,p,6);
  822.                Output_record ||= SUBSTR(Base64_xlate_table,Output_byte+1,1);
  823.           END;
  824.           IF LENGTH(Output_record) = MAXLENGTH(Output_record) THEN
  825.           DO;
  826.                WRITE FILE(Output_file) FROM(Output_record);
  827.                Output_record = '=';
  828.           END;
  829.           ELSE
  830.                Output_record ||= '=';
  831.           WRITE FILE(Output_file) FROM(Output_record);
  832.      END;
  833.  
  834.      CLOSE FILE(Output_file),
  835.           FILE(Input_file);
  836.  Skip_file:
  837.      RETURN;
  838.  END BASE64_ENCODE;
  839.  %PAGE;
  840.  /* Internal subroutine to split a list of file names, delimited by
  841.     plus signs, into an array of distinct file names */
  842.  Split_concatenated_files:
  843.  PROC(File_list,Filename_array);
  844.  
  845.      DCL  File_list                     CHAR(*) NONASGN,
  846.           Filename_array(*)             CHAR(*) VAR CTL;
  847.  
  848.      DCL  Filename_delim                CHAR(1) VALUE('+'),
  849.           (i,d,p)                       BIN FIXED(31,0) UNSIGNED,
  850.           (HBOUND,LBOUND,LENGTH,MIN,SEARCH,SUBSTR,TALLY) BUILTIN;
  851.  
  852.      /* Determine the no. of path/filenames in the list.
  853.         Allocate that many, with a suitable maximum length */
  854.      ALLOC Filename_array(1:TALLY(File_list,Filename_delim)+1)
  855.                CHAR(MIN(260,LENGTH(File_list)));
  856.  
  857.      /* Start scan in column 1 */
  858.      p = 1;
  859.      /* Extract all but the last path/filename */
  860.      DO i = LBOUND(Filename_array,1) TO HBOUND(Filename_array,1) - 1;
  861.           /* Scan for a delimiter */
  862.           d = SEARCH(File_list,Filename_delim,p);
  863.           /* Extract that path/filename */
  864.           Filename_array(i) = SUBSTR(File_list,p,d-p);
  865.           /* Increment leftmost scan position */
  866.           p = d + 1;
  867.      END;
  868.      /* Take care of the last one */
  869.      Filename_array(HBOUND(Filename_array,1)) = SUBSTR(File_list,p);
  870.  
  871.      RETURN;
  872.  END Split_concatenated_files;
  873.  
  874.  END UUXXCODE;
  875.