/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * END COPYRIGHT BLOCK **/
/*///////////////////////////////////////////////////////*/
/*/                                                     /*/
/*/ David Tompkins (mailto:dt@netscape.com)             /*/
/*/ Netscape Communications Corp.                       /*/
/*/ 10/7/97                                             /*/
/*/                                                     /*/
/*/ Help.cgi: a supercharged tutor.cgi                  /*/
/*/                                                     /*/
/*///////////////////////////////////////////////////////*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>

#ifdef XP_UNIX
#   include <dirent.h>
#   define SYS_DIR DIR
#   define SYS_DIRENT struct dirent
#   define dir_open opendir
#   define dir_read readdir
#   define dir_close closedir
#   define dir_name(de) (de->d_name)
#   define FILE_SEP '/'
#   define STRNCASECMP strncasecmp
#   define STRCASECMP  strcasecmp
#else /* XP_WIN32 */
#   include <prio.h>
#   define SYS_DIR PRDir
#   define SYS_DIRENT PRDirEntry
#   define dir_open PR_OpenDir
#   define dir_read(d) PR_ReadDir(d, PR_SKIP_BOTH)
#   define dir_close PR_CloseDir
#   define dir_name(de) (de->name)
#   define FILE_SEP '/'
#   define STRNCASECMP _strnicmp
#   define STRCASECMP  _stricmp
#endif

#include "config.h"

#define PATH_LENGTH    1024
#define DIR_ARG        "helpdir"
#define TOKEN_ARG      "token"
#define GENLIB_ARG     "genlib"
#define MAPFILE_ARG    "mapfile"
#define DEBUG_ARG      "debug"
#define FRAMESET_FILE  "frameset.html"
#define CONTENTS_FILE  "contents.htm"
#define LIBRARY_FILE   "library.html"
#define CONTENT_FRAME  "infotopic"
#define DIR_VAR        "HelpDirectory"
#define HELPWIN_VAR    "HelpWindow"
#define TOKEN_FILE     "index.map"
#define HEADER_FILE    "header.html"
#define SKIN_HEADER_FILE    "skin-header.html"
#define FOOTER_FILE    "footer.html"
#define HEAD_BLOCK     "<head>"
#define FRAME_BLOCK    "<frame "
#define TITLE_BLOCK    "<title>"
#define DOCLIST_BLOCK  "<doclist>"
#define DOC_BLOCK      "<document>"
#define DOCLIST_EBLOCK "</doclist>"
#define COMMENT_CHAR   ';'
#define BASE_DIR       ".." /* for relative URLs */
#define HELPSUBDIR     "help" /* subdirectory of MANUALDIR */
#define TEST_DEPTH     10
#define DEFAULT_LANG   "en"

static char *localeList;
static char *locale;
static int debugPrintout = 0;
static int didContentHeader = 0;

static int
safe_snprintf(char *buf, size_t size, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = vsnprintf(buf, size, fmt, ap);
    va_end(ap);
    buf[size-1] = 0;
    return ret;
}

int
parse_query_string(char *qs, char **name[], char **val[])
{
   char *t, *p;
   int cnt = 1;

   if (!qs)
      return -1;

   /* count args */
   t = qs;
   while ((t = strchr(t, '&')))
   {
      t++;
      cnt++;
   }

   *name = (char**)malloc(sizeof(char *) * cnt);
   *val  = (char**)malloc(sizeof(char *) * cnt);

   if (!*name || !*val)
      return -1;

   cnt = 0;
   for (t = strtok(strdup(qs), "&") ; t != NULL ; t = strtok(NULL, "&"))
   {
      if ((p = strchr(t, '=')))
      {
	 (*name)[cnt] = (char*)malloc(sizeof(char) * ((p-t) + 1));
	 strncpy((*name)[cnt], t, (p-t));
	 (*name)[cnt][(p-t)] = '\0';
	 (*val)[cnt++] = strdup(p+1);
	 continue;
      }

      (*name)[cnt]  = strdup(t);
      (*val)[cnt++] = strdup("");
   }

   return (cnt);
}

char *
find_arg(char *nm, char *name[], char *val[], int cnt)
{
   int i;

   for (i = 0 ; i < cnt ; i++)
   {
      if (!STRCASECMP(name[i], nm))
	 return val[i];
   }

   return (NULL);
}

void
error(char *msg)
{
   printf("Help Error: %s\n", msg);
}

int
error_exit(char *msg)
{
   printf("Content-type: text/html\n\n");
   printf("<title>Help Error</title><h1>Help Error</h1>");
   printf("Your request could not be fulfilled.<br><br>");
   printf("<b>Reason :</b> %s<br>", msg);
   exit(0) ;
}

static char *
caseless_strstr(char *string, char *pattern)
{
   char *p;

   if (!string || !pattern)
      return (NULL);

   for (p = string ; *p ; p++)
   {
      if (!STRNCASECMP(p, pattern, strlen(pattern)))
	 return (p);
   }

   return (NULL);
}

static char * 
getProductName(const char *nickname) {
        if (!strcmp(nickname, "admin")) {
            return "Administration";
        }
        if (!strcmp(nickname, "https")) {
            return "Enterprise";
        }
        if (!strcmp(nickname, "httpd")) {
            return "FastTrack";
        }
        if (!strcmp(nickname, "msg")) {
            return "Messaging";
        }
        if (!strcmp(nickname, "news")) {
            return "News";
        }
        if (!strcmp(nickname, "proxy")) {
            return "Proxy";
        }
        if (!strcmp(nickname, "lmspd")) {
            return "Media";
        }
        if (!strcmp(nickname, "slapd")) {
            return "Directory";
        }
        if (!strcmp(nickname, "cert")) {
            return "Certificate";
        }
        if (!strcmp(nickname, "compass")) {
            return "Compass";
        }
        if (!strcmp(nickname, "catalog")) {
            return "Catalog";
        }
        if (!strcmp(nickname, "rds")) {
            return "Rds";
        }
        if (!strcmp(nickname, "calendar")) {
            return "Calendar";
        }

        return NULL;
    }

/* determine the locale supported from from the list obtained from 
the client */
char *getLocale() {
   char *loc;
   char path[PATH_LENGTH];
   SYS_DIR *dir = NULL;

   /* Go through the list of locales in the HTTP_ACCEPT_LANGUAGE
      env var and check if at least one of them is available
      in the server installation  */
   loc = strtok(strdup(localeList), ",");
   while (1){
      if (loc) {
	safe_snprintf(path, sizeof(path), "%s%c%c%c", MANUALDIR, FILE_SEP, loc[0], loc[1]);
	if ((dir = dir_open(path))) {
	  /* return only the first 2 characters of the language code e.g. return en for en-US */
	  loc[2] = '\0';
	  dir_close(dir);
	  return loc;
	}
      }
      loc = strtok(NULL, ",");
      if (!loc) {
         /* default to English (en)  */
         safe_snprintf(path, sizeof(path), "%s%c%s", MANUALDIR, FILE_SEP, DEFAULT_LANG);
         if ((dir = dir_open(path))) {
	    dir_close(dir);
            return DEFAULT_LANG;
	 } else {
            error_exit("No help available for any of the languages set for the browser");
	 }
      }
   }
}


/* the standard help response handler for clients as of Console 4.5 */
static int
no_frame_help(char *name[], char *val[], int cnt, char *product, char *content)
{
   char  path[PATH_LENGTH];
   char  base[PATH_LENGTH];
   FILE *file  = NULL;

   if (debugPrintout)
   {
	   printf( "New help<P>\n" );
   }

   /* read and flush the header to stdout */
   /* try to read the "skin" header file first, then fall back to the default */
   safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale, FILE_SEP,
                   product, FILE_SEP, SKIN_HEADER_FILE);
   if (!(file = fopen(path, "r")))
   {
       safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale, FILE_SEP,
                     product, FILE_SEP, HEADER_FILE);
   }
   if (!file && (!(file = fopen(path, "r"))))
   {
           safe_snprintf(base, sizeof(base), "unable to open file: %s", path);
           if (debugPrintout)
           {
                   printf("%s<P>\n", base);
                   fflush( stdout );
           }
           return error_exit(base);
   }
   if (debugPrintout)
   {
           printf("reading file: %s<P>\n", path);
   }
   if (!didContentHeader)
   {
           printf("Content-type: text/html\n\n");
   }
   while (fgets(path, PATH_LENGTH, file))
   {
           fputs(path, stdout);
   }

   fclose(file);

   /* Open the target file and return the contents */
   safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale, FILE_SEP,
		   product, FILE_SEP, content);
   if (!(file = fopen(path, "r")))
   {
	   safe_snprintf(base, sizeof(base), "unable to open file: %s", path);
	   if (debugPrintout)
	   {
		   printf("%s<P>\n", base);
		   fflush( stdout );
	   }
	   return error_exit(base);
   }
   if (debugPrintout)
   {
	   printf("reading file: %s<P>\n", path);
   }
   while (fgets(path, PATH_LENGTH, file))
   {
	   fputs(path, stdout);
   }

   fclose(file);

   /* read and flush the footer to stdout */
   safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale, FILE_SEP,
                   product, FILE_SEP, FOOTER_FILE);
   if (!(file = fopen(path, "r")))
   {
           safe_snprintf(base, sizeof(base), "unable to open file: %s", path);
           if (debugPrintout)
           {
                   printf("%s<P>\n", base);
                   fflush( stdout );
           }
           return error_exit(base);
   }
   if (debugPrintout)
   {
           printf("reading file: %s<P>\n", path);
   }
   while (fgets(path, PATH_LENGTH, file))
   {
           fputs(path, stdout);
   }

   /* finished */
   fflush(stdout);
   fclose(file);
   return 0;
}


/* the standard help response handler for clients prior to Console 4.5 */
static int
oldhelp(char *name[], char *val[], int cnt, char *product, char *content,
		char *token)
{
   char  path[PATH_LENGTH];
   FILE *frameset  = NULL;

   if (debugPrintout)
   {
	   printf( "Old help\n\n" );
   }

   /* open a frameset file, either from the product dir, or the master file
    * in the help dir. */

   safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale, FILE_SEP, product, FILE_SEP, FRAMESET_FILE);
   if (debugPrintout)
   {
	   printf("opening frameset file: %s<P>\n", path);
   }
   if (!(frameset = fopen(path, "r")))
   {
      /* product frameset failed, so try global one */
      safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, HELPSUBDIR, FILE_SEP, FRAMESET_FILE);
      if (!(frameset = fopen(path, "r")))
	  {
		  if (debugPrintout)
		  {
			  printf("unable to open a valid frameset file: %s<P>\n", path);
			  fflush( stdout );
		  }
		  return error_exit("unable to open a valid frameset file");
	  }
   }

   if (debugPrintout)
   {
	   printf("reading frameset file: %s<P>\n", path);
   }

   /* spit out the output, inserting src= into the CONTENT_FRAME frame block */

   if (!didContentHeader)
   {
	   printf("Content-type: text/html\n\n");
   }

   while (fgets(path, PATH_LENGTH, frameset))
   {
      char *f, *p;

      if ((f = caseless_strstr(path, HEAD_BLOCK)))
      {
	 /* insert some javascript in the head block */

         p = path;
         while (p < f + strlen(HEAD_BLOCK))
	    putchar(*(p++));

         printf("<script type=\"text/javascript\">\n");
         printf("%s=\"%s\";\n", DIR_VAR, product); /* HelpProduct = product name */
	 printf("%s=window;\n", HELPWIN_VAR); /* top.helpwin = window */
         printf("</script>\n");

         while (*p)
	    putchar(*(p++));

         continue;
      }

      if (!(f = caseless_strstr(path, FRAME_BLOCK)) || !caseless_strstr(path, CONTENT_FRAME))
      {
	 fputs(path, stdout);
	 continue;
      }

      /* insert src= element into CONTENT_FRAME frame block */

      p = path;
      while (p < f + strlen(FRAME_BLOCK))
	 putchar(*(p++));

      printf("src=\"%s%c%s%c%s%c%s\" ", BASE_DIR, FILE_SEP, locale, FILE_SEP, product, FILE_SEP, content);

      while (*p)
	 putchar(*(p++));
   }

   /* finished */
   fflush(stdout);
   fclose(frameset);
   return 0;
}

/* given the name of a directory and the name of a file/directory in that parent
   return true if the given file/directory exists */
static int
file_or_dir_exists(const char *parent, const char *name)
{
    SYS_DIR       *dp;
    SYS_DIRENT    *d;
    int ret = 0; /* default to false */

    dp = dir_open(parent);
    /* loop through all directory entries until we find the one that matches */
    while (dp && name && !ret && (d = dir_read(dp))) {
	const char *entname = dir_name(d);
	/* exclude "." and ".." from scanning */
	if (strcmp(entname, ".") && strcmp(entname, "..")) {
	    /* set ret to true if the name matches, which terminates the while loop */
	    ret = !strcmp(name, entname);
	}
    }
    if (dp) {
	dir_close(dp);
    }

    return ret;
}

/* the standard help response handler */
static int
help(char *name[], char *val[], int cnt)
{
   char *product = NULL;
   char *token   = NULL;
   char *content = NULL;
   char *mapfile = NULL;

   char  path[PATH_LENGTH];
   FILE *tokenfile = NULL;
   int  newstyle = 1;

   if (!(product = find_arg(DIR_ARG, name, val, cnt)))
   {
	   if (debugPrintout)
	   {
		   printf( "product argument not in URL<P>\n" );
		   fflush(stdout);
	   }
      return error_exit("product argument not found");
   }

   if (!(token = find_arg(TOKEN_ARG, name, val, cnt)))
   {
	   if (debugPrintout)
	   {
		   printf( "token argument not in URL<P>\n" );
		   fflush(stdout);
	   }
      return error_exit("token argument not found");
   }

   if (find_arg(DEBUG_ARG, name, val, cnt))
   {
      debugPrintout = 1;
   }

   if (debugPrintout)
   {
	   printf("Content-type: text/html\n\n");
	   didContentHeader = 1;
   }

   if (debugPrintout)
   {
	   printf( "Product: %s<P>\n", product?product:"" );
	   printf( "Token: %s<P>\n", token?token:"" );
   }

   /* Newer clients can specify a token map file */
   mapfile = find_arg(MAPFILE_ARG, name, val, cnt);
   if ((NULL == mapfile) || (0 == *mapfile))
   {
	   mapfile = TOKEN_FILE;
	   newstyle = 0;
   }
   else if (debugPrintout)
   {
	   printf( "Map file parameter: %s<P>\n", mapfile );
   }

   /* first, see if locale directory exists in its parent */
   safe_snprintf(path, sizeof(path), "%s", MANUALDIR);
   if (!file_or_dir_exists(path, locale)) {
       if (debugPrintout) {
	   printf( "No help files for locale [%s]<P>\n", locale?locale:"");
	   fflush(stdout);
       }
       return error_exit("Failed to open help for locale.");
   }
   /* ok, locale directory is good, check product directory */
   safe_snprintf(path, sizeof(path), "%s%c%s", MANUALDIR, FILE_SEP, locale);
   if (!file_or_dir_exists(path, product)) {
       if (debugPrintout) {
	   printf( "No help files for product [%s] in locale [%s]<P>\n", product?product:"", locale);
	   fflush(stdout);
       }
       return error_exit("Failed to open help for given product.");
   }
   /* Ok, product directory exists, check for the mapfile */
   safe_snprintf(path, sizeof(path), "%s%c%s%c%s", MANUALDIR, FILE_SEP, locale,
                        FILE_SEP, product);
   if (!file_or_dir_exists(path, mapfile)) {
       if (debugPrintout) {
	   printf( "No map file [%s]  for product [%s] in locale [%s]<P>\n", mapfile?mapfile:"", product, locale);
	   fflush(stdout);
       }
       return error_exit("Failed to open help for given product.");
   }
   safe_snprintf(path, sizeof(path), "%s%c%s%c%s%c%s", MANUALDIR, FILE_SEP, locale,
                        FILE_SEP, product?product:"", FILE_SEP, mapfile);
   if (debugPrintout)
   {
	   printf( "Opening map file: %s<P>\n", path );
   }

   if (!(tokenfile = fopen(path, "r")))
   {
	   if (debugPrintout)
	   {
		   printf( "Failed to open map file: %s<P>\n", path );
		   fflush( stdout );
	   }
      return error_exit("Failed to open Token file.");
   }

   /* translate token */
   /* match the token from the appropriate token.map file */
   while (token && fgets(path, PATH_LENGTH, tokenfile))
   {
      char *p, *q;

      if (path[0] == COMMENT_CHAR)
	 continue;
      
      if (STRNCASECMP(path, token, strlen(token)))
	 continue;
      
      if (!(p = strchr(path, '=')))
	 continue;

      p++;

      /* snip leading whitespace */
      while ((*p == ' ') || (*p == '\t'))
	 p++;

      /* snip trailing junk */
      q = p + strlen(p) - 1;
      while ((*q == '\n') || (*q == '\r') || (*q == ' ') || (*q == '\t'))
	 *(q--) = '\0';
      
      content = strdup(p);
      break;
   }

   fclose(tokenfile);

   if (!content)
   {
	   if (debugPrintout)
	   {
		   printf( "Failed to translate the token: %s\n<BR>\n", token?token:"" );
		   fflush( stdout );
	   }
      return error_exit("unable to translate the token");
   }

   if (debugPrintout)
   {
	   fflush( stdout );
   }

   /* Older clients use the frameset model, newer ones just want the
	  undecorated Help file */
   if (newstyle)
   {
	   no_frame_help( name, val, cnt, product, content );
   }
   else
   {
	   oldhelp( name, val, cnt, product, content, token );
   }
   return 0;
}

static void 
dumpTitle(char *name) {
   printf("<TR>");
   printf("<TD COLSPAN=2 BGCOLOR=\"#9999CC\">");
   printf("<FONT FACE=\"Palatino, Serif\" SIZE=\"+1\" COLOR=\"white\">");
   printf("<B>" CAPBRAND " %s Server</B></FONT>", name);
   printf("</TD>");
   printf("</TR>");
}

static void
check_directory(char *dirpath, char *prefix, char *suffix, int dirDepth)
{
   char           path[PATH_LENGTH];
   FILE          *fp;
   SYS_DIR       *dp;
   SYS_DIRENT    *d;


   /* first, check for contents.htm in this directory */

   safe_snprintf(path, sizeof(path), "%s%c%s", dirpath, FILE_SEP, CONTENTS_FILE);

   if ((fp = fopen(path, "r")))
   {
      /* look for the title block within the first TEST_DEPTH lines */

      char  line[PATH_LENGTH];
      char *title, *p;
      int   i = 0;

      while ((i++ < TEST_DEPTH) && fgets(line, PATH_LENGTH, fp))
      {
         if (!(title = caseless_strstr(line, TITLE_BLOCK)))
	    continue;
        
	 title += strlen(TITLE_BLOCK);

	 if ((p = strchr(title, '<')))
	    *p = '\0';

	 printf("%s<a href=%s>%s</a>%s\n", prefix, path, title, suffix);
         break;
      }

      fclose(fp);
   }

   /* now, try to recurse through lower directories */

   if (!(dp = dir_open(dirpath)))
      return;

   while ((d = dir_read(dp)))
   {
      /* We should try to check if it is a directory first...*/

      /* don't check anything starting with . */
      if (dir_name(d)[0] == '.')
	 continue;
     
      if (dirDepth == 0) {
         if (getProductName(dir_name(d)) != NULL)
            dumpTitle(getProductName(dir_name(d)));
         
      }
      safe_snprintf(path, sizeof(path), "%s%c%s", dirpath, FILE_SEP, dir_name(d));
      check_directory(path, prefix, suffix, dirDepth+1);

      if (dirDepth == 0)
         printf("<TR> <TD>&nbsp; </TD> <TD>&nbsp; </TD> </TR>");
   }

   dir_close(dp);
}

/* generates the library contents list */
static int
genlib()
{
   FILE *template;
   char  line[PATH_LENGTH];
   char  base[PATH_MAX];
   char  library_file[PATH_MAX];

   printf("Content-type: text/html\n\n");

   /* open the library template file from the help dir. */
   safe_snprintf(library_file, sizeof(library_file), "%s%c%s%c%s", MANUALDIR, FILE_SEP,
                 HELPSUBDIR, FILE_SEP, LIBRARY_FILE);
   if (!(template = fopen(library_file, "r")))
      return error_exit("unable to open a valid library template file");

   /* Spit the template file back out, except substitute the generate
    * listings for the <doclist> block */

   while (fgets(line, PATH_LENGTH, template))
   {
      char *prefix;
      char *suffix;
      char *endp;

      if (STRNCASECMP(line, DOCLIST_BLOCK, strlen(DOCLIST_BLOCK)))
      {
	 fputs(line, stdout);
	 continue;
      }

      prefix = line + strlen(DOCLIST_BLOCK);

      if (!(endp = caseless_strstr(prefix, DOC_BLOCK)))
      {
         error("invalid &lt;doclist&gt; block in library template");
	 continue;
      }

      suffix = endp + strlen(DOC_BLOCK);

      *endp = '\0';

      if (!(endp = caseless_strstr(suffix, DOCLIST_EBLOCK)))
      {
         error("invalid &lt;doclist&gt; block in library template");
	 continue;
      }

      *endp = '\0';

      safe_snprintf(base, sizeof(base), "%s%c%s", MANUALDIR, FILE_SEP, locale);
      check_directory(base, prefix, suffix, 0);
   }

   fflush(stdout);
   fclose(template);
   return 0;
}

int
main(int argc, char *argv[])
{
   char **name;
   char **val;
   int    cnt;

   if (!(localeList = getenv("HTTP_ACCEPT_LANGUAGE"))) 
	/*  default to en  */
	localeList = strdup("en");

   cnt = parse_query_string(getenv("QUERY_STRING"), &name, &val);

   locale = getLocale();

   if (find_arg(GENLIB_ARG, name, val, cnt))
      return genlib();

   return help(name, val, cnt);
}
