/*****************************************************************************/
/*
                                   liner.c

Display a file as plain-text (actually escaped HTML), prefixing each line with
an ascending line number in square brackets.  The response does everything it
can to ensure content is not cached so any repeat access should be just as
expensive as the original :-)  Two-pass formatting to allow cut-and-paste
unencumbered by line numbers.

Adding a hash fragment integer to the URL (e.g. "/liner.c#100") will cause the
browser to move the window to that line number in the source (if it exists).

Appending "$.txt" or "$.htm" to any file name (in fact "$.anythingatall") is
allowed to try and get certain operating systems (whose name cannot be spoken)
to treat the content as text (i.e. not using the returned content-type, rather
the trailing three characters of the file name).  If the supplied file name
cannot be opened the utility removes any faux "$.extension" and attempts to
open the resulting file name.

Can be accessed using /cgi-bin/liner/path/to/file.txt or abbreviated using the
WASD_CONFIG_MAP mapping rule

   script /liner/* /cgi-bin/liner*

and used as /liner/path/to/file.txt

A directory structure (e.g. source code tree) can have this mapping applied to
all file accesses using the "?httpd=index" facility.  For example

   /wasd_root/src/*.*?httpd=index&script=liner


SYNTAX HIGHLIGHTING
-------------------
To add syntax highlighting for displayed text go to

   https://highlightjs.org/download/

and download a custom package with the languages of interest.

Restore the ZIPed archive into the source directory (or other web server
accessible location).

   $ SET DEFAULT WASD_ROOT:[src]   
   $ CREATE /DIRECTORY [.highlightjs]
   $ SET DEFAULT [.highlightjs]
   $ CREATE .www_hidden
   ^Z
   $ UNZIP <location>HIGHLIGHT.ZIP

To activate LINER.C syntax highlighting define a logical name with the path to
the highlight directory.

   $ DEFINE /SYSTEM /EXEC WASD_LINER_HIGHLIGHTJS_PATH -
            "/wasd_root/src/highlightjs/"

Themes and rendering may be explored at

   https://highlightjs.org/static/demo/

An alternate theme may be set using the logical name.

   $ DEFINE /SYSTEM /EXEC WASD_LINER_HIGHLIGHTJS_THEME "xcode"

And by inclusion in the directory URL.

   /wasd_root/src/*.*?httpd=index&script=liner&theme=xcode

File types may be explicitly provided with a language type.  The format is a
one or more comma-separated <.type>=<language> defined in a logical name.  For
example; the following renders .COB and .COM files as plain text.

   $ DEFINE /SYSTEM /EXEC WASD_LINER_HIGHLIGHTJS_TYPE -
            ".cob=plaintext,.com=plaintext"

A wrapper procedure and symbols alternatively could be used.

   $! CGI_BIN:LINER.COM
   $ WASD_LINER_HIGHLIGHTJS_PATH  = "/wasd_root/src/highlightjs/"
   $ WASD_LINER_HIGHLIGHTJS_THEME = "xcode"
   $ WASD_LINER_HIGHLIGHTJS_TYPE  = ".cob=plaintext,.com=plaintext"
   $ MCR CGI_EXE:LINER


BUILD DETAILS
-------------
$ @BUILD_LINER BUILD  !compile+link
$ @BUILD_LINER LINK   !link-only


COPYRIGHT
---------
Copyright (C) 2013-2022 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
---------------
20-JAN-2022  MGD  v2.0.0, adapt for https://highlightjs.org
13-MAY-2015  MGD  v1.0.1, bugfix; closing </body> and </html>
23-MAY-2013  MGD  v1.0.0, initial (q&d) development
*/
/*****************************************************************************/

#define SOFTWAREVN "2.0.0"
#define SOFTWARENM "LINER"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  error VAX no longer implemented
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* CGILIB header file */
#include "cgilib.h"

/* macros */
#define FI_LI __FILE__, __LINE__

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

main (int argc, char** argv)
       
{
   int  idx, LineCount = 0;
   char  *aptr, *cptr, *hptr, *lptr, *sptr, *tptr;
   char  LangClass [256],
         Line [1024],
         HtmlEnc [sizeof(Line)*6];  /* worst case */
   FILE  *fptr;

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

   CgiLibEnvironmentInit (argc, argv, 0);
   CgiLibResponseSetErrorMessage ("Reported by LINER");

   if (strcmp (CgiLibVar ("REQUEST_METHOD"), "GET"))
   {
      CgiLibResponseSetErrorStatus (501);
      CgiLibResponseError (FI_LI, 0, "Only supports GET method!");
      return;
   }

   aptr = CgiLibVar ("PATH_INFO");
   if (!*aptr)
   {
      CgiLibResponseSetErrorStatus (501);
      CgiLibResponseError (FI_LI, 0, "Supply a file name!");
      return;
   }

   sptr = CgiLibVar ("PATH_TRANSLATED");
   if (!*sptr)
   {
      CgiLibResponseSetErrorStatus (501);
      CgiLibResponseError (FI_LI, 0, "PATH_TRANSLATED?");
      return;
   }

   cptr = CgiLibVar ("CONTENT_TYPE");
   if (strncasecmp (cptr, "text/", 5))
   {
      CgiLibResponseSetErrorStatus (501);
      CgiLibResponseError (FI_LI, 0, "Not a \"text/\" MIME type.");
      return;
   }

   if ((fptr = fopen (sptr, "r", "shr=get")) == NULL)
   {
      for (cptr = sptr; *cptr; cptr++);
      while (cptr > sptr && *cptr != '$' && *cptr != ']') cptr--;
      if (*cptr == '$' && *(cptr+1) == '.')
      {
         /* chop these off and see if that opens */
         *cptr-- = '\0';
         while (cptr > sptr && *cptr != ']' && *cptr != '.' && *cptr != '$')
            cptr--;
         if (cptr > sptr && *cptr == '.')
         {
            if (cptr > sptr && *(cptr-1) == '^')
            {
               /* eliminate the EFS escape */
               for (; *cptr; cptr++) *(cptr-1) = *cptr;
               *(cptr-1) = '\0';
            }
            fptr = fopen (sptr, "r", "shr=get");
         }
         else
         if (cptr > sptr && *cptr == '$')
         {
            /* non-EFS convert substituted dollar back to a period */
            *cptr = '.';
            fptr = fopen (sptr, "r", "shr=get");
         }
      }
      if (fptr == NULL)
      {
         CgiLibResponseSetErrorStatus (404);
         CgiLibResponseError (FI_LI, vaxc$errno, aptr);
         return;
      }
      lptr = cptr;
   }
   else
   {
      for (cptr = sptr; *cptr; cptr++);
      while (cptr > sptr && *cptr != '.' && *cptr != ']') cptr--;
      lptr = cptr;
   }

   if (hptr = getenv ("WASD_LINER_HIGHLIGHTJS_PATH"))
   {
      if (!(tptr = getenv ("WASD_LINER_HIGHLIGHTJS_THEME"))) tptr = "default";
      if (cptr = CgiLibVarNull ("FORM_THEME")) tptr = cptr;
      aptr = getenv ("WASD_LINER_HIGHLIGHTJS_TYPE");
   }
   else
      aptr = NULL;

   if (hptr && aptr)
   {
      while (*aptr)
      {
         cptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (!*aptr) break;
         *aptr++ = '\0';
         if (!strcasecmp (lptr, cptr))
         {
            lptr = aptr;
            while (*aptr && *aptr != ',') aptr++;
            *aptr = '\0';
            aptr = "\a";
            break;
         }
         while (*aptr && *aptr != ',') aptr++;
         if (*aptr) aptr++;
      }
      if (*aptr == '\a')
      {
         sprintf (LangClass, " class=\"language-%s\"", lptr);
         lptr = LangClass;
      }
      else
         lptr = "";
   }
   else
      lptr = "";

   if ((stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin")) == NULL)
      exit (vaxc$errno);

   CgiLibResponseSetStreamMode (1);

   aptr = CgiLibVar ("PATH_INFO");

   CgiLibResponseHeader (200, "text/html",
"Expires: Fri, 13 Jan 1978 14:00:00 GMT\r\n\
Cache-Control: no-cache, no-store, private, max-age=0, max-stale=0, \
must-revalidate, pre-check=0, post-check=0\r\n\
Pragma: no-cache\r\n");

   for (cptr = aptr; *cptr; cptr++);
   while (cptr > aptr && *cptr != '/') cptr--;
   if (*cptr == '/') cptr++;

   fprintf (stdout,
"<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<title>%s</title>\n\
<meta name=\"generator\" content=\"%s\">\n\
<style type=\"text/css\">\n\
body { color:black; background-color:white; }\n\
a { color:gray; }\n\
pre { display:block; margin:0; }\n\
.lnum { float:left; margin-right:1em;%s }\n\
</style>\n",
           cptr, SOFTWAREID,
           hptr ? " margin-top:1em;" : "");

   if (hptr)
      fprintf (stdout,
"<link rel=\"stylesheet\" href=\"%sstyles/%s.min.css\">\n\
<script src=\"%shighlight.min.js\"></script>\n\
<script>hljs.highlightAll();</script>\n",
               hptr, tptr, hptr);

   fprintf (stdout,
"</head>\n\
<body>\n\
<pre class=\"lnum\">");

   while (fgets (Line, sizeof(Line), fptr) != NULL)
   {
      LineCount++;
      if (LineCount <= 9999)
         cptr = "<a name=\"%d\">[%04.04d]</a>\n";
      else
      if (LineCount <= 99999)
         cptr = "<a name=\"%d\">[%05.05d]</a>\n";
      else
         cptr = "<a name=\"%d\">[%06.06d]</a>\n";
      fprintf (stdout, cptr, LineCount, LineCount);
   }

   rewind (fptr);
   fprintf (stdout, "</pre>\n<pre><code%s>", lptr);

   while (fgets (Line, sizeof(Line), fptr) != NULL)
   {
      sptr = HtmlEnc;
      for (cptr = Line; *cptr; cptr++)
      {
         switch (*cptr)
         {
             /* control characters with a bullet symbol */
             case  0 : case  1 : case  2 : case  3 :
             case  4 : case  5 : case  6 : case  7 :
             case  8 :                     case 11 :
             case 12 :           case 14 : case 15 :
             case 16 : case 17 : case 18 : case 19 :
             case 20 : case 21 : case 22 : case 23 :
             case 24 : case 25 : case 26 : case 27 :
             case 28 : case 29 : case 30 : case 31 :
                        *(unsigned long*)sptr = '&bul'; sptr += 4;
                        *(unsigned short*)sptr = 'l;';  sptr += 2; break;
             /* HTML reserved */
             case '<' : *(unsigned long*)sptr = '&lt;'; sptr += 4; break;
             case '>' : *(unsigned long*)sptr = '&gt;'; sptr += 4; break;
             case '&' : *(unsigned long*)sptr = '&amp'; sptr += 4;
                        *sptr++ = ';'; break;
             /* the great unwashed */
             default : *sptr++ = *cptr;
         }
      }
      *sptr = '\0';
      fputs (HtmlEnc, stdout);
   }

   fputs ("</code></pre>\n</body>\n</html>\n", stdout);

   fclose (fptr);
}

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

