SLAC's REXX WWW CGI Function Library

Скачать скрипты с Яндекс.Диск

Last Update: 3 Mar 1997. URL=https://www.slac.stanford.edu/slac/www/tool/cgi-rexx/


To call the following functions from your script you will need to include the following in your script:

CALL PUTENV 'REXXPATH=/afs/slac/www/slac/www/tool/cgi-rexx'

Index of REXX CGI Functions
Function Owner Group Bytes Updated Comment
testfinger cottrell sf 1018 Nov 11 18:06 Example of a script to provide a finger function
minimal cottrell sf 459 Mar 3 1996 Simple Illustration of a Form CGI Script
testinput Mwww oh 1306 Mar 1 1996 Example to show processing of input
cleanquery cottrell sf 707 Feb 21 18:37 Removes all occurences of unassigned variables from CGI query string
cgierror cottrell sf 524 Nov 11 18:04 Reports an error and returns
cgidie cottrell sf 535 Mar 2 1996 Reports an error and Exits
chkpwd cottrell sf 1664 Nov 11 18:06 Check a username/password combination
delquery cottrell sf 904 Mar 3 15:29 Remove item from CGI query string
deweb cottrell sf 1549 Nov 11 18:06 Converts ASCII Hex coded %XX to ASCII characters
formatdate cottrell sf 1344 Feb 21 18:37 Parses the date expression given and returns in Oracle format
fullurl cottrell sf 531 Feb 21 18:37 Returns the complete CGI query URL
getowner cottrell sf 384 Feb 21 18:36 Returns owner of a specified file
getfullhost cottrell sf 414 Feb 21 19:26 Returns the fully qualified domain name of the local host
htmlbreak cottrell sf 785 Feb 21 18:37 Breaks a long line into lines appropriate for HTML parsing
htmlbot cottrell sf 135 Jan 20 1996 Insert boiler plate at end of page
htmltop cottrell sf 305 Nov 11 18:19 Insert title and h1 header at top of page
httab cottrell sf 2991 Nov 11 18:06 Convert a tab delimited file to an HTML table
methget cottrell sf 153 Nov 21 1995 Returns true if the form is using METHOD="GET"
methpost cottrell sf 158 Nov 21 1995 Returns true if the form is using METHOD="POST"
myurl cottrell sf 239 Nov 11 18:06 Adds the URL of the script to the page
oraenv crane bs 656 Feb 7 1996 Sets up the SLAC Oracle/REXX environment
printheader cottrell sf 1192 Feb 18 15:02 Inserts the Content-type header
printvariables cottrell sf 629 Mar 3 1996 Adds a listing of the Form name=value& variables to the page
readform cottrell sf 531 Jan 26 1996 Reads a Form's "GET" or "POST" input and returns it decoded
readpost cottrell sf 1697 Nov 11 18:06 Reads the standard input from a form with METHOD="POST"
slacfnok cottrell sf 1711 Nov 11 18:06 Identifies the allowed visibility of a file
striphtml cottrell sf 618 Feb 21 18:36 Removes HTML markup from an input string
suspect cottrell sf 555 Nov 11 18:06 Provides an error message if the input string contains a suspect character
webify cottrell sf 1038 Nov 11 18:06 Encodes special characters in hex ASCCII %XX form
wraplines cottrell sf 716 Feb 21 18:36 Breaks long lines into lines appropriate for terminal output

cgi-lib.rxx

Les Cottrell. Last Update: 3 Mar 1997



/* REXX Routines to Manipulate CGI input
cottrell@slac.stanford.edu
https://www.slac.stanford.edu/~cottrell.html/cottrell.html

These routines are modelled on a set of Perl routines from
S.E.Brenner@bioc.cam.ac.uk,  
with some additions suggested by "Gateway Programming I: ..." in
"HTML and CGI Unleashed" by John December and Mark Ginsberg, published
by Sams/Macmillan.

For more information on Steve's functions, see:
    https://www.bio.cam.ac.uk/web/form.html       
    https://www.seas.upenn.edu/~mengwong/forms/   
For more information on "HTML and CGI Unleashed" see
    https://www.rpi.edu/~decemj/works/wdg.html

This  document  and/or portions  of  the  material and  data
furnished herewith,  was developed under sponsorship  of the
U.S.  Government.  Neither the  U.S. nor the U.S.D.O.E., nor
the Leland Stanford Junior  University, nor their employees,
nor their  respective contractors, subcontractors,  or their
employees,  makes  any  warranty,  express  or  implied,  or
assumes  any  liability   or  responsibility  for  accuracy,
completeness  or usefulness  of any  information, apparatus,
product  or process  disclosed, or  represents that  its use
will not  infringe privately-owned  rights.  Mention  of any
product, its manufacturer, or suppliers shall not, nor is it
intended to, imply approval, disapproval, or fitness for any
particular use.   The U.S. and  the University at  all times
retain the right to use and disseminate same for any purpose
whatsoever.

Copyright (c) Stanford University 1995, 1996.

Permission granted to use and modify this library so long as the
copyright above is maintained, modifications are documented, and
credit is given for any use of the library.

The main functional differences of the REXX version to the Perl
version are:

*ReadParse is replaced by ReadForm which returns the results in a 
different fashion. ReadForm returns the results as a string, whereas
ReadParse uses an associative variable.  This difference is
necessitated since REXX does not support returning associative
(stem) variables from external functions.

*PrintVariables takes as input a string rather than an asociative
variable.  This difference is driven by the expectation that it
will be used with a string returned from ReadForm.

*/
#!/usr/local/bin/rxx
/* The above line indicates that the code is a 
REXX script and where the REXX interpreter is 
to be found. This may be different at your site.    

Sample CGI Script in  Uni-REXX, invoke from:
https://www.slac.stanford.edu/cgi-wrap/finger?cottrell*/

Fail=PUTENV('REXXPATH=/afs/slac/www/slac/www/tool/cgi-rexx')
/* The above line tells the REXX interpreter 
where to find the external REXX library 
functions, such as PrintHeader, HTMLTop, 
DeWeb and HTMLBot.            */ 

SAY PrintHeader()  /*Put out Content-type stuff*/
SAY '<body bgcolor="FFFFFF">'

In=DeWeb(TRANSLATE(GETENV('QUERY_STRING'),' ','+'))
  /*Decode + signs to spaces and hex %XX to chars*/
SAY HTMLTop('Finger' In)'<pre>'
Valid=' abcdefghijklmnopqrstuvwxyz'
Valid=Valid||'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
Valid=Valid||'0123456789-_/.@'

V=VERIFY(In,Valid) /*Check input is valid*/
IF V\=0 THEN
  SAY 'Bad char('SUBSTR(In,V,1)')in:"'In'"'
ELSE ADDRESS COMMAND '/usr/ucb/finger' In
SAY HTMLBot() /*Put out trailer boilerplate*/
EXIT

#!/usr/local/bin/rxx
/*  Minimalist http form and script           */
F=PUTENV("REXXPATH=/afs/slac/www/slac/www/tool/cgi-rexx")
SAY PrintHeader(); SAY '<body bgcolor="FFFFFF">'
Input=ReadForm()
IF Input='' THEN DO  /*Part 1*/
  SAY HTMLTop('Minimal Form')
  SAY '<form><input type="submit">',
      '<br>Data: <input name="myfield">'
END
ELSE DO              /*Part 2*/
  SAY HTMLTop('Output from Minimal Form') 
  SAY PrintVariables(Input)
END
SAY HTMLBot()

#!/usr/local/bin/rxx
/* The above line indicates that the code is a 
REXX script and where the REXX interpreter is 
to be found. This may be different at your site.    

Sample CGI Script in  Uni-REXX, invoke from:
https://www.slac.stanford.edu/cgi-wrap/testinput*/

Fail=PUTENV('REXXPATH=/afs/slac/www/slac/www/tool/cgi-rexx')
/* The above line tells the REXX interpreter 
where to find the external REXX library 
functions, such as PrintHeader, HTMLTop, 
ReadPost, DeWeb and HTMLBot.            */ 

StdinFile='/tmp/stdin'_GETPID()/*Get unique name*/
  /*_GETPID() provides the process Id in Uni-REXX*/
SAY PrintHeader(); SAY HTMLTop('testinput')
/*********************************************** */
/*Read input from the various sources.           */
/*Note that we preserve or save                  */
/*input in case we need to send it to another    */
/*script. If so we can restore the stdin for the */
/*the called command by  using the REXX command: */
/*ADDRESS UNIX script '<' StdinFile              */ 
/*********************************************** */      

PARSE ARG Parms/*QUERY_STRING input for non FORMS*/
SAY 'Command line parms="'Parms'"'
SAY '<br>Standard input="'ReadPost(StdinFile)'"'
SAY '<br>PATH_INFO="'GETENV('PATH_INFO')'"'
SAY '<br>QUERY_INPUT="'GETENV('QUERY_STRING')'"'
EXIT

/* This package includes (in alphabetical order): */

/*
 * CleanQuery(cgi_string)
 *
 * Removes all occurences of unassigned variables from a CGI query string.
 * CGI query strings are of the form VAR1=value1&VAR2=value2&...  It is
 * possible for a Web form to generate "VAR=" elements, with no assignment,
 * which in many cases are removable from the query without effect.  The
 * remaining elements are preserved in order and in case.
 *
 * Example: CleanQuery("A=5&B=&C=&B=abc") returns "A=5&B=abc".
 *
 * 970221  Michael Kelsey
 */

CleanQuery:	PROCEDURE
  Parse arg Qstring

  Qnew = ''
  Do while Qstring <> ''
     Parse var Qstring var '=' val '&' Qstring
     If val <> '' Then Qnew = Qnew'&'var'='val
  End
  Qnew = STRIP(Qnew,'B','&')
Return Qnew

/* CgiError
Prints out an error message which contains 
appropriate headers, markup, etcetera.
Parameters:
If no parameters, gives a generic error message
Otherwise, the first parameter will be the title 
and the rest will be given as the body
*/
CgiError: PROCEDURE; PARSE ARG Title, Body
  IF Title='' THEN 
    Title='Error: script' MyURL(),
          'encountered fatal error.'
  SAY '<html><head><title>'Title'</title></head>'
  SAY '<body><h1>'Title'</h1>'
  IF Body\='' THEN SAY Body
  SAY '</body></html>'
RETURN ''

/* CgiDie
   Identical to CgiError, but also quits with the 
   passed error message. This appears to work on SunOS. 
   On AIX 3.2 it appears to be  necessary to enter an
   extra carriage return if cgidie is called from a 
   REXX script initiated from the command line.
*/
CgiDie: PROCEDURE
  PARSE ARG Title, Body
  Fail=CgiError(Title, Body)
  Pid=_GETPID()
  Kill=_KILL(Pid,9)
  SAY 'Kill='Kill
  SAY 'Error killing process id',
      Pid', system error:' _errno()
  SAY _sys_errlist(_errno())
  SAY 'Process not killed.'
  EXIT

chkpwd: PROCEDURE; PARSE ARG PasswordFile, UserName, Password
/* Check's user's password (fails if there is no such user).
Returns zero if the password is correct. Otherwise returns
an error message and message number.

The parameters PasswordFile, UserName and Password must only contain
alphanumerics plus .-_/@,

This function uses the CERN httpd password file, that is maintained
using the htadm program which is part of the CERN httpd distribution.
At SLAC htadm is located at /afs/slac/g/www/bin/htadm-sun or
/afs/slac/g/www/bin/htadm-aix
Before using this function the password admin must use the
htadm function to create the password file and enter the password
for the selected username.

Example:
Msg=chkpwd('/afs/slac/u/sf/cottrell/www/test.pwd','cottrell',password)

*/
IF PasswordFile='' THEN RETURN 'chkpwd(1): null PasswordFile name given!'
IF LINES(PasswordFile)=0 THEN RETURN "chkpwd(2): either can't find or found an empty PasswordFile" PasswordFile
IF UserName=''     THEN RETURN 'chkpwd(3): needs a UserName, but none was provided!'
IF Password=''     THEN RETURN 'chkpwd(4): needs a Password, but none was provided!'
IF LENGTH(Password)>8 THEN RETURN 'chkpwd(5): password must be 8 characters or less!'
Parms=PasswordFile Username Password
IF Suspect(Parms)\='' THEN RETURN 'chkpwd(6):' Suspect(Parms) 'in input parameters!' 
Fail=POPEN('/afs/slac/g/www/bin/htadm-sun -check' Parms)
IF QUEUED()>0      THEN PARSE PULL Line
ELSE RETURN 'chkpwd('10+Fail'): htadm failed, maybe a problem with passwordfile' PasswordFile'!'
IF Fail\=0         THEN RETURN 'chkpwd(-'Fail'): username/password' Line
IF Line='Correct'  THEN RETURN 0
ELSE RETURN Line

/*
 * DelQuery(cgi_string,varname)
 *
 * Removes all occurences of a given CGI query variable from the input
 * string.  CGI query strings are of the form VAR1=value1&VAR2=value2&...
 * The matching is not case sensitive, and the result is returned with
 * the same case as the input string.
 *
 * Example: DelQuery("A=5&B=2&C=3&B=ABC","B") returns "A=5&C=3".
 *
 * 970221  Michael Kelsey
 * 970303  Steve Meyer, replace UPPER with TRANSLATE, add Qstring to all RETURNs
 */

DelQuery:	PROCEDURE
  Parse arg Qstring, Varname
  Parse upper arg Qup, Vup	/* Case-insensitive version for matching */

  If Varname = '' Then Return Qstring

  Do while POS(Vup'=',Qup) > 0		/* Case-insensitive matching */
     a = POS(Vup'=',Qup) ; b = POS('&',Qup,a)
     If b = 0 Then b = LENGTH(Qup)
     Qstring = STRIP(SUBSTR(Qstring,1,a-1)||SUBSTR(Qstring,b+1),'T','&')
     Qup = TRANSLATE(Qstring)
  End
Return Qstring

DeWeb: PROCEDURE; PARSE ARG In, Op
/* *******************************************
DeWeb converts hex encoded (e.g. %3B=semi-colon) 
characters in the In string to the equivalent 
ASCII characters and returns the decoded string.
If the 2 characters following a % sign do not
represent a hexadecimal 2 digit number, then 
the % and following 2 characters are returned
unchanged. If the string terminates with a % then
the % sign is returned unchanged. If the final
two characters in the string are a % sign 
followed by a single hexadecimal digit then  
they are returned unchanged.

The optional Op argument contains a set of 
characters which allows you to tell DeWeb to:
'+' convert plus signs (+) to spaces
    in the input before the hex decoding is done.
'*' convert asterisks (*) to percent signs (%) 
    after the decoding.  This option
    is often used with Oracle.
   
Authors: Les Cottrell & Steve Meyer - SLAC

Examples:
  SAY DeWeb('%3Cpre%3e%20%%25Loss  %Util%') 
  results in:  '<pre> %%Loss  %Util%'
  SAY DeWeb('%3cpre%3eName++Address*','*+')
  results in   '<pre>Name  Address%'
******************************************* */
IF POS('+',Op)\=0 THEN In=TRANSLATE(In,' ','+')
Start=1; Decoded=''; String=In
DO WHILE POS('%',String)\=0
   PARSE VAR String Pre'%'+1 Ch +2 In
   IF DATATYPE(Ch,'X') & LENGTH(Ch)=2 THEN 
        Ch=X2C(Ch)
   ELSE DO; In=Ch||In; Ch='%'; END
   Decoded=Decoded||Pre||Ch
   Start=LENGTH(Decoded)+1
   In=Decoded||In
   String=SUBSTR(In,Start)
END
IF POS('*',Op)\=0 THEN In=TRANSLATE(In,'%','*')
RETURN In

/*
 * FormatDate(DTexpr)
 *
 * Parses the date expression given, and converts it to a standard
 * format DD-MON-YY:HH:MM:SS, for use by Oracle.  The date may be
 * given in any of the formats
 *
 *	mm/dd/yy	mm/dd/yyyy
 *      dd/mm/yy	dd/mm/yyyy
 *      dd-Mon-yy	dd-Mon-yyyy
 *
 * and with an optional hh:mm[:ss] time string, with hours in 12- or
 * 24-hour format, appended with a colon.
 *
 * 970221  Michael Kelsey
 */

FormatDate:	PROCEDURE
  Parse Arg DTexpr
  Parse Var DTexpr date ':' time

  months = 'JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC'
  days =   '31  29  31  30  31  30  31  31  30  31  30  31'

  Parse Var date mm '/' dd '/' yy		/* eg 10/18/96 */
  If Datatype(mm,'W') = 0 | Datatype(dd,'W') = 0 ,
     | Datatype(yy,'W') = 0 Then Do

     Parse Upper Var date dd '-' mon '-' yy	/* eg 18-oct-96 */
     mm = WordPos(mon,months)

     If Datatype(dd,'W') = 0 | Datatype(yy,'W') = 0 | mm = 0 ,
     Then Return ''
  End
  Else Do
     dd = Format(dd)
     mm = Format(mm)
     If dd > Word(days,mm) | dd < 1 Then Do
        parse value dd mm with mm dd
        If dd > Word(days,mm) | dd < 1 Then Call DateError DTexpr
     End
  End

  If yy > 1900 Then yy = RIGHT(yy,2)

  date = dd"-"Word(months,mm)"-"yy

  If time='' then result = date		/* Return full date-time string */
  Else result = date':'time
Return result

/*
 * FullURL()
 *
 * Rebuilds complete CGI query URL from CGI environment variables.  The
 * "REFERER_URL" string stops at the name of the CGI script, eliminating
 * any path or query information.  This makes it difficult to embed the
 * original query for reference in script output.
 *
 * 970221  Michael Kelsey
 */

FullURL:	PROCEDURE
  path  = GetEnv('PATH_INFO')
  query = GetENV('QUERY_STRING')

  FullURL = MyURL()
  If path <> '' Then FullURL = FullURL||path
  If query <> '' Then FullURL = FullURL'?'query
Return FullURL

/*
 * GetOwner(file)
 *
 * Return username of owner of specified file.  This function is ONLY
 * valid for UniREXX (Rexx running on Unix).  It uses the POPEN routine
 * to fetch file information from 'ls'.
 *
 * 970221  Michael Kelsey
 */

GetOwner:	PROCEDURE
   Parse Arg fname
   Call POPEN 'ls -dloL' fname		/* This is UniREXX Specific! */
   Parse pull . . owner .
Return owner



/*
 * GetFullHost()
 *
 * Returns the fully qualified domain name (FQDN) of the local host,
 * using the UniREXX specific _GETHOSTNAME and _GETHOSTBYNAME functions.
 */
GetFullHost:	PROCEDURE
   fullhost = _GETHOSTNAME()		/* This is UniREXX Specific! */

   If POS('.',host) = 0 Then Do		/* Get domain information */
     Call _GETHOSTBYNAME fullhost,'hinfo.'
     fullhost = hinfo.H_NAME
   End
Return fullhost



/*
 * HTMLBreak(long_string[,len])
 *
 * Breaks the specified "very long" message string into lines appropriate
 * for HTML parsing.  Each "line" will be up to _len_ characters long
 * (80 if len not specified), and will be broken at word boundaries (spaces
 * or tabs).  The string will have HTML break tags "<BR>" inserted at each
 * line break point.
 *
 * 970221  Michael Kelsey
 */

HTMLBreak:	PROCEDURE
  Parse arg message, len
  If len='' Then len = 80

  broken = ''
  br = ''
  Do while message <> ''
     cut = LASTPOS(' ',LEFT(message,len))	/* Find word break at end */
     if cut = 0 Then cut = len-1

     broken = broken||br||LEFT(message,cut)
     If broken<>'' Then br = '<BR>'		/* Add breaks to later lines */
     message = SUBSTR(message,cut+1)
  End
Return broken

/* HtmlBot
   Returns the </body>, </html> codes for 
   the bottom of every HTML page
*/
HtmlBot: PROCEDURE
  RETURN '</body></html>'

/* HtmlTop
 Returns the head of a document and the  
 beginning of the body with the title and a 
 body h1 header as specified by the parameter.
 Example: SAY HTMLBot('Heading for WWW Page')
*/
HtmlTop: PROCEDURE; PARSE ARG Title
  RETURN '<html><head><title>'Title'</title></head><body><h1>'Title'</h1>'

HTtab: PROCEDURE; PARSE ARG InFn, OutFn, Delim, Options
/* httab - Converts a tab delimited file into an HTML Table */
/*
   httab - Converts a tab delimited file into an HTML Table

  .....................................................................
  Command Format:
       CALL HTtab(InFn, OutFn)
       
Where: InFn is the fully qualified inout filename of the tab delimited
       file.
       IF InFn is equal to '-' THEN input is read from stdin.
       OutFn is the fully qualified name of the output filename where the
       HTML table will be written.  The default for OutFn is standard
       output. If OutFn="=" then the output filename=InFn||'.html',
       unles InFn='-' in which case OutFn='/tmp/qall.html'.      
       If OutFn='-' then no output file is written.
       Delim specifies the tab delimter to be used.  The default
       is '09'X an horizontal tab.

The converted file contents are returned by HTTab.  If an error
is encountered (e.g. no Input filename is provided), then an
HTML error message is returned with the first character being an
exclamation mark (!).
       
Examples:
 Msg=HTTab(Fn),1,1); IF SUBSTR(Msg,1,1)='!' THEN DO; SAY Msg'</body></html>'; EXIT; END
 SAY HTTab(Fn,'=')

Note this function can be much more simply done in Perl.

Please send comments and/or suggestion to Les Cottrell.
*/
/* **************************************************************** */
/* Owner(s): Les Cottrell, Jan 23, 1996                             */
/* Revision History:                                                */
/* **************************************************************** */

   /* ********************************************************** */
   /* Get the  parameters                                        */
   /* ********************************************************** */
   IF InFn=''        THEN RETURN '!<br>No input file specified.</br>'
   IF InFn='-'       THEN InFn=''
   IF LINES(InFn)=0  THEN RETURN "!<br>Can't find file' InFn 'or it is empty.</br>"
   Out=1
   IF OutFn='='      THEN DO;
      IF InFn='' THEN OutFn='/tmp/qall.html'
      ELSE            OutFn=InFn'.html'
   END
   ELSE IF OutFn='-' THEN Out=0
   IF Delim=''       THEN Delim='09'X /* Horizontal Tab */

   /* *********************************************************** */
   /* Do the conversion.                                          */
   /* *********************************************************** */
   Body='<CAPTION><b>'InFn'</b></CAPTION><TABLE Border>'
   IF Out THEN CALL LINEOUT(OutFn,Body,1)
   DO L=1 BY 1 WHILE LINES(InFn)>0
      Line=LINEIN(InFn); LineO='<TR>'
      DO WHILE Line\=''
         PARSE VAR Line Pre (Delim) Line 
         LineO=LineO||'<TD>'Pre'</TD>'
      END
      Body=Body||'0a'X||LineO||'</TR>'
      IF Out THEN CALL LINEOUT(OutFn,LineO||'</TR>')
   END
   Body=Body||'0a'x||'</TABLE>'
   IF Out THEN DO
      CALL LINEOUT(OutFn,'</TABLE>')
      CALL LINEOUT(OutFn) /*Close File*/
   END
   RETURN Body
   

/* MethGet
   Return true if this cgi call was using the GET request, false otherwise
*/
MethGet: PROCEDURE
   RETURN 'GET'=GETENV('REQUEST_METHOD')
   

/* MethPost
   Return true if this cgi call was using the POST request, false otherwise
*/
MethPost: PROCEDURE
  RETURN 'POST'=GETENV('REQUEST_METHOD')
     

/* MyURL
   Returns a URL to the script
*/
MyURL: PROCEDURE
   IF GETENV('SERVER_PORT')\='80' THEN 
        Port=':'GETENV('SERVER_PORT')
   ELSE Port=''
   Url='https://'GETENV('SERVER_NAME')||Port
   RETURN Url||GETENV('SCRIPT_NAME')
   

OraEnv: PROCEDURE

   /* oraenv - set up Oracle database environment variables */     
   /* George Crane, January 1996                            */

   Address command

   /* Set up the correct environment variables for */
   /* communcations with Oracle and set some       */
   /* required environment variables for Oracle    */
 
   call popen "grep 'setenv ORACLE_HOME' /usr/local/bin/coraenvp"
   If queued() = 0 Then exit
   parse pull . . home
   path = GetEnv('PATH')
   rc = putenv("LIBHOME="home"/lib")
   rc = putenv("PATH="path":"home"/bin")
   rc = putenv("TWO_TASK=SLAC_TCP")
   rc = putenv("TNS_ADMIN="home"/network/admin")
 
   Return 0


/* PrintHeader
   Returns the magic line which tells WWW what    
   kind of document is to follow.  If no first  
   argument is provided, then the default 
   document type is HTML, and this is returned together
   with an extra newline to terminate the HTTP header.
   Otherwise the first argument provides the type/subtype.
   If the second argument is 1 then a Location: 
   header is returned instead of the text/html header.
   If the second argument is present (i.e. not null)
   but not equal to 1, then the first argument is returned. 
   If the second argument is not provided and there is
   a non null first argument then 
   Content-type: <first-argument>nl is returned.
   Examples:
     SAY PrintHeader()
     SAY PrintHeader('https://www.halcyon.com/hedlund/cgi-faq/',1)
     SAY PrintHeader('Status: 305 Document moved',0)
     SAY PrintHeader('application/postscript')   
*/
PrintHeader: PROCEDURE; PARSE ARG Content, IsURL
  nl=d2c(10) /*N.B. 10 is the decimal code for a newline*/
  IF Content='' THEN RETURN 'Content-type: text/html'nl
  ELSE IF IsUrl=1 THEN RETURN 'Location:' Content
  ELSE IF IsUrl\='' THEN RETURN Content
  ELSE RETURN 'Content-type:' Content||nl
  

/* PrintVariables
Decodes the Form data block variables 
in the In argument (which are in the format 
key1=value1&key2=value2&...) and returns them 
in a nicely formatted HTML string.
Example:  
  SAY PrintVariables(GETENV('QUERY_STRING'))
*/
PrintVariables: PROCEDURE; PARSE ARG In
  n='0A'X; /*Newline*/; Out=n||'<dl compact>'||n
  DO I=1 BY 1 UNTIL In=''
    /* Split into key and value */
    PARSE VAR In Key.I'='Val.I'&' In
    /* Convert %XX from hex to alphanumeric*/
    Key.I=DeWeb(Key.I,'+'); Val.I=DeWeb(Val.I,'+')
    Out=Out'<dt><b>'Key.I'</b>'n,
           '<dd><i>'Val.I'</i><br>'n
  END I
RETURN Out||'</dl>'||n

/* ReadForm
Reads in GET or POST data, converts plus signs (+) 
to spaces. Does not convert %XX encoded characters 
to unescaped text since this would confuse 
encoded ampersands and equal signs with
those used to separate the fields in the 
name=value& pairs.
Returns the converted input if there is any, 
else returns ''.
*/
ReadForm: PROCEDURE
  /* Read in text */
  IF MethGet() THEN In=GETENV('QUERY_STRING')
  ELSE IF MethPost() THEN 
     In=CHARIN(,1,GETENV('CONTENT_LENGTH'))
  ELSE RETURN ''
RETURN TRANSLATE(In,' ','+')

ReadPost: PROCEDURE; PARSE ARG StdinFile
  /******************************************** */
  /*Read HTML FORM POST input (if any) from     */
  /*standard input. Note that if the caller     */
  /*provides a filename then we save the input  */
  /*in case we need to send it to another       */
  /*script. If so we can restore the stdin for  */
  /*the called command by  using the command:   */
  /*ADDRESS UNIX script '<' StdinFile           */
  /*A good way to get a unique filename to save */
  /*the standard input in, is to use the process*/
  /*id. For example in Uni-REXX:                */
  /* StdinFile='/tmp/stdin'_GETPID()            */
  /* Post=ReadPost(StdinFile)                   */
  /*If a StdinFile is specified, but ReadPost   */
  /*is unable to write the standard input to    */
  /*StdInFile, then ReadPost EXITs.             */ 
  /*ReadPost returns the POST input if the      */
  /*REQUEST_METHOD="POST" else it returns null. */
  /*ReadPost also returns a null string if the  */
  /*REQUEST_METHOD="POST" but there is no input */
  /*in the standard input.                      */
  /*N.b. the returned Post input does NOT have  */
  /*plus signs (+) converted to spaces or hex   */
  /*ASCII %XX encodings converted to characters.*/  
  /******************************************** */      
  In=''
  IF GETENV('REQUEST_METHOD')="POST" THEN DO
    N=GETENV('CONTENT_LENGTH')
    IF N='' THEN RETURN In
    In=CHARIN(,1,GETENV('CONTENT_LENGTH'))
    IF StdinFile\='' THEN DO
      IF CHAROUT(StdinFile,In,1) \=0 THEN DO
        SAY "500: Can't write all POST chars!" 
        EXIT
      END
      Fail=CHAROUT(StdinFile)/*Close the file*/
    END
  END
RETURN In


/* SLACfnOK
Checks that the filename is OK to be made accessible.
IF OK then it returns a null string, else it returns a
string with the reason why the file is not accessible.
*/
SLACfnOK: PROCEDURE; PARSE ARG Fn

Valid='abcdefghijklmnopqrstuvwxyz0123456789'
Valid=Valid||'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_/'
CharNb=VERIFY(Fn,Valid)
IF CharNb\=0                                      THEN
  RETURN 'contains an invalid character ('SUBSTR(Fn,CharNb,1)')'

IF POS('..',Fn)\=0                                THEN
  RETURN '.. in filename'
IF LEFT(Fn,1)='-'                                 THEN
  RETURN '- at start of filename'
IF POS('SLACONLY',TRANSLATE(Fn))\=0 THEN DO
  IF SUBSTR(GETENV('REMOTE_ADDR'),1,7)\='134.79.' &,
     GETENV('REMOTE_ADDR')\='' THEN
     RETURN 'SLAC only access'
END
IF SUBSTR(Fn,1,10)='/afs/slac/' THEN
  Fn='/afs/slac.stanford.edu/'||SUBSTR(Fn,11)
IF SUBSTR(Fn,1,27)='/afs/slac.stanford.edu/www/'  THEN RETURN ''
IF POS('public_html/',Fn)\=0                      THEN RETURN ''
IF SUBSTR(GETENV('REMOTE_ADDR'),1,7)\='134.79.' &,
  GETENV('REMOTE_ADDR')\=''                      THEN
  RETURN 'file not accessible from outside SLAC'
IF SUBSTR(Fn,1,25)='/usr/local/scs/net/cando/'    THEN RETURN ''
IF Fn='/etc/printcap'                             THEN RETURN ''
IF SUBSTR(,1,28)='/var/www/log/httpd.prod/err.'   THEN RETURN ''
IF Fn=''                                          THEN RETURN ''
IF LEFT(Fn,5)='/tmp/'                       THEN RETURN ''
IF Fn='/var/www/harvest/gatherers/slac/log.errors' THEN RETURN ''
IF Fn='/var/www/harvest/gatherers/slac/log.gatherer' THEN RETURN ''
IF POS('/tmp/htlog',Fn)\=0                        THEN RETURN ''
ELSE RETURN 'file not in access list'

/*
 * StripHTML(markup)
 *
 * Simplistically removes HTML markup from an input string.  No use of
 * context or semantic information is done -- every <.....> tag is just
 * removed.
 *
 * Example: StripHTML("<H1><IMG SRC="babar.gif"> BaBar Experiment</H1>")
 *          returns " BaBar Experiment"
 *
 * 970221  Michael Kelsey
 */

StripHTML:	PROCEDURE
  Parse arg in

  out = ''
  tag = 0
  i = 1
  Do until i > LENGTH(in)
    ch = SUBSTR(in,i,1)
    tag = tag | (ch = '<')		/* Beginning of HTML tag */
    If tag=0 Then out = out||ch
    tag = tag & (ch <> '>')		/* End of HTML tag */
    i = i + 1
  End
Return out

Suspect: PROCEDURE; PARSE ARG Input
/*
Checks that the Input string is composed of valid
characters which should not cause problems with 
shell expansions. Suspect returns null if Input 
is composed of valid characters otherwise it 
returns an error message.
Example:
IF Suspect(In)\='' THEN DO; 
   SAY Suspect(In) 'in:' '"'In'"'; EXIT; END
*/
Valid=' abcdefghijklmnopqrstuvwxyz' ||,
       'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
Valid=Valid||'0123456789-_/.@,'
V=VERIFY(Input,Valid)
IF V\=0 THEN 
   RETURN 'Invalid character('SUBSTR(Input,V,1)')'
ELSE RETURN ''

Webify: PROCEDURE; PARSE ARG Input
/* ***************************************************
Some characters may not be usable in a URL since its
use may conflict with a reserved character. In such
cases the character may be encoded with a % followed
by its ASCII hexadecimal equivalent code.  Webify
encodes the Input provided in the argument for
a selected set of ASCII characters (see the variable
Esc) and provides the encoded Input as output.
*************************************************** */
Esc='%'||XRANGE('00'X,'$')||XRANGE('&','/'),
       ||XRANGE(':','@')||XRANGE('[','`'),
       ||XRANGE('{','FF'X) /* List of chars to be encoded*/
DO UNTIL Esc=''/*Check for chars to be escaped*/
   PARSE VAR Esc Char 2 Esc
   P=POS(Char,Input); Enc='%'C2X(Char)
   Start=1; Decoded=''
   DO WHILE POS(Char,SUBSTR(Input,Start))\=0
      String=SUBSTR(Input,Start)
      PARSE VAR String Pre (Char) Input
      Start=LENGTH(Decoded||Pre||Enc)+1
      Input=Decoded||Pre||Enc||Input
      Decoded=Decoded||Pre||Enc
   END
END
RETURN Input

/*
 * CALL WrapLines long_string [,len]
 *
 * Breaks the specified "very long" message string into lines appropriate
 * for terminal output.  Each line will be up to _len_ characters long
 * (80 if len not specified), and will be broken at word boundaries (spaces
 * or tabs).  Each resulting line is written to standard output.
 *
 * 970221  Michael Kelsey
 */

WrapLines:	PROCEDURE
  Parse arg message, len
  If len='' Then len = 80

  Do while message <> ''
     cuts = LASTPOS(' ',LEFT(message,len))	/* Find word break at end */
     cutt = LASTPOS(d2c(9),LEFT(message,len))
     cut = MAX(cuts,cutt)
     if cut = 0 Then cut = len-1

     Say LEFT(message,cut)
     message = SUBSTR(message,cut+1)
  End
Return