/*****************************************************************************/
/*
                                   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


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


COPYRIGHT
---------
Copyright (C) 2013-2015 Mark G.Daniel
This program, 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 3, or any later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY
---------------
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 "1.0.1"
#define SOFTWARENM "LINER"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#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>

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

/* macros */
#define FI_LI __FILE__, __LINE__

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

main (int argc, char** argv)
       
{
   int  LineCount = 0;
   char  *aptr, *cptr, *sptr;
   char  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;
   }

   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;
      }
   }

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

   CgiLibResponseSetStreamMode (1);

   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; }\n\
</style>\n\
</head>\n\
<body>\n\
<pre class=\"lnum\">",
           cptr, SOFTWAREID);

   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);
   fputs ("</pre>\n<pre>", stdout);

   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 ("</pre>\n</body>\n</html>\n", stdout);

   fclose (fptr);
}

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

