/*****************************************************************************/
/*
                            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-2021 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


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
}

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