/*****************************************************************************/
/*
                            CGIplus_cgivar.c

Generic, SELF-CONTAINED FUNCTION to return the values of a CGI variable
regardless of whether it is used in a standard CGI environment or a WASD
CGIplus environment.  Also automatically switches WASD V7.2 and later servers
into 'struct' mode for significantly improved performance.  To "force" the use
of 'record' mode (perhaps for performance comparison purposes) assign a
non-empty environment variable (symbol or logical) named CGIPLUS_VAR_RECORD.

Call with 'VarName' empty ("") to synchronize CGIplus requests.  This waits for
a CGIplus variable stream, checks if it's in 'record' or 'struct' mode, reads
the stream appropriately before returning ready to provide variables.

DO NOT modify the character string returned by this function.  Copy to other
storage if this is necessary.  The behaviour is indeterminate if the returned
values are modified in any way!

All CGIplus variables may be returned by making successive calls using a
'VarName' of "*" (often useful when debugging).  CGIVAR_NONE is returned when
variables exhausted.

Also see the more extensive set of functions offered by [SRC.MISC]CGILIB.C

Required standard library header files: stdlib.h, stdio.h, string.h
Required function prototype: char* CgiVar (char*);


COPYRIGHT
---------
Copyright (C) 2001 Mark G.Daniel (mark.daniel@wasd.vsm.com.au)
This software comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2.


VERSION HISTORY
---------------
01-APR-2001  MGD  initial
*/
/*****************************************************************************/

char* CgiVar (char *VarName)

{
#  ifndef CGIVAR_STRUCT_SIZE
#     define CGIVAR_STRUCT_SIZE 8192
#  endif
#  ifndef CGIVAR_NONE
#     define CGIVAR_NONE NULL
#  endif
#  define SOUS sizeof(unsigned short)

   static int  CalloutDone,
               StructLength;
   static char  *CgiPlusVarRecordPtr,
                *IsCgiPlusPtr,
                *NextVarNamePtr;
   static char  StructBuffer [CGIVAR_STRUCT_SIZE];
   static FILE  *CgiPlusIn;

   int  Length;
   char  *bptr, *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

#ifdef DEBUGIT
   if (DEBUGIT)
      if (VarName != NULL && VarName[0])
         fprintf (stdout, "CgiVar() |%s|\n",
                  VarName == NULL ? "NULL" : VarName);
#endif

   if (VarName == NULL || !VarName[0])
   {
      /* initialize */
      StructLength = 0;
      NextVarNamePtr = StructBuffer;
      if (VarName == NULL) return (CGIVAR_NONE);
   }

   /* detect the CGIplus environment (once) */
   if (IsCgiPlusPtr == NULL)
      if ((IsCgiPlusPtr = getenv ("CGIPLUSEOF")) == NULL)
         IsCgiPlusPtr = "";

   if (VarName[0])
   {
      /***************************/
      /* return a variable value */
      /***************************/

      if (!IsCgiPlusPtr[0])
      {
         /* standard CGI environment */
         if ((cptr = getenv (VarName)) == NULL) cptr = CGIVAR_NONE;
#ifdef DEBUGIT
         if (DEBUGIT)
            fprintf (stdout, "CGI |%s|\n", cptr == NULL ? "NULL" : cptr);
#endif
         return (cptr);
      }

      /* hmmm, not initialized */
      if (!StructLength) return (CGIVAR_NONE);

      if (VarName[0] == '*')
      {
         /* return each CGIplus variable in successive calls */
         if (!(Length = *(unsigned short*)NextVarNamePtr))
         {
            NextVarNamePtr = StructBuffer;
            return (CGIVAR_NONE);
         }
         sptr = (NextVarNamePtr += SOUS);
         NextVarNamePtr += Length;
         return (sptr);
      }

      /* return a pointer to this CGIplus variable's value */
      for (bptr = StructBuffer; Length = *(unsigned short*)bptr; bptr += Length)
      {
         sptr = (bptr += SOUS);
         for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++)
            if (toupper(*cptr) != toupper(*sptr)) break;
         /* if found return a pointer to the value */
         if (!*cptr && *sptr == '=')
         {
#ifdef DEBUGIT
            if (DEBUGIT) fprintf (stdout, "CGIplus |%s|\n", sptr+1);
#endif
            return (sptr+1);
         }
      }
      /* not found */
#ifdef DEBUGIT
      if (DEBUGIT)
         fprintf (stdout, "CGIplus |%s|\n",
                  CGIVAR_NONE == NULL ? "NULL" : CGIVAR_NONE);
#endif
      return (CGIVAR_NONE);
   }

   /*****************************/
   /* get the CGIplus variables */
   /*****************************/

   /* cannot "sync" in a non-CGIplus environment */
   if (!VarName[0] && !IsCgiPlusPtr[0]) return (CGIVAR_NONE);

   /* the CGIPLUSIN stream can be left open */
   if (CgiPlusIn == NULL)
      if ((CgiPlusIn = fopen (getenv("CGIPLUSIN"), "r")) == NULL)
         exit (vaxc$errno);

   /* get the starting record (the essentially discardable one) */
   for (;;)
   {
      cptr = fgets (StructBuffer, sizeof(StructBuffer), CgiPlusIn);
      if (cptr == NULL) exit (vaxc$errno);
      /* if the starting sentinal is detected then break */
      if (*(unsigned short*)cptr == '!\0' ||
          *(unsigned short*)cptr == '!\n' ||
          (*(unsigned short*)cptr == '!!' && isdigit(*(cptr+2)))) break;
   }

#ifdef DEBUGIT
   /* MUST be done after reading the synchronizing starting record */
   if (DEBUGIT)
      fprintf (stdout, "Content-Type: text/plain\n\n%s", StructBuffer);
#endif

   /* detect the CGIplus "force" record-mode environment variable (once) */
   if (CgiPlusVarRecordPtr == NULL)
      if ((CgiPlusVarRecordPtr = getenv ("CGIPLUS_VAR_RECORD")) == NULL)
         CgiPlusVarRecordPtr = "";

   if (*(unsigned short*)cptr == '!!' && !CgiPlusVarRecordPtr[0])
   {
      /********************/
      /* CGIplus 'struct' */
      /********************/

      /* get the size of the binary structure */
      StructLength = atoi(cptr+2);
      if (StructLength <= 0 || StructLength > sizeof(StructBuffer))
         exit (SS$_BUGCHECK);

      if (!fread (StructBuffer, 1, StructLength, CgiPlusIn))
         exit (vaxc$errno);
   }
   else
   {
      /*********************/
      /* CGIplus 'records' */
      /*********************/

      /* reconstructs the original 'struct'ure from the records */
      sptr = (bptr = StructBuffer) + sizeof(StructBuffer);
      while (fgets (bptr+SOUS, sptr-(bptr+SOUS), CgiPlusIn) != NULL)
      {
         /* first empty record (line) terminates variables */
         if (bptr[SOUS] == '\n') break;
         /* note the location of the length word */
         cptr = bptr;
         for (bptr += SOUS; *bptr && *bptr != '\n'; bptr++);
         if (*bptr != '\n') exit (SS$_BUGCHECK);
         *bptr++ = '\0';
         if (bptr >= sptr) exit (SS$_BUGCHECK);
         /* update the length word */
         *(unsigned short*)cptr = bptr - (cptr + SOUS);
      }
      if (bptr >= sptr) exit (SS$_BUGCHECK);
      /* terminate with a zero-length entry */
      *(unsigned short*)bptr = 0;
      StructLength = (bptr + SOUS) - StructBuffer;
   }

#ifdef DEBUGIT
   if (DEBUGIT)
   {
      fprintf (stdout, "%d\n", StructLength);
      for (bptr = StructBuffer; Length = *(unsigned short*)bptr; bptr += Length)
         fprintf (stdout, "|%s|\n", bptr += SOUS);
   }
#endif

   if (!CalloutDone && !CgiPlusVarRecordPtr[0])
   {
      /* provide the CGI callout to set CGIplus into 'struct' mode */
      fflush (stdout);
      fputs (CgiPlusEscPtr, stdout);
      fflush (stdout);
      /* the leading '!' indicates we're not going to read the response */
      fputs ("!CGIPLUS: struct", stdout);
      fflush (stdout);
      fputs (CgiPlusEotPtr, stdout);
      fflush (stdout);
      /* don't need to do this again (the '!!' tells us what mode) */
      CalloutDone = 1;
   }

   return (CGIVAR_NONE);

#  undef SOUS
}

/*****************************************************************************/
