
/* db.c */
/* Andrew Davison (ad@ratree.psu.ac.th) */
/* December 1996 */

/* This program is passed forms details by:
      http://fivedots.coe.psu.ac.th/~ad/code/sdb/dbform.html

   At most PATNO string patterns are extracted from the
   details, which are then used to search the file FNM.

   By using "fgrep", the lines which contain all the
   strings are found. At most RESNO of these matches
   are returned to the user as an HTML page.

   Details about the user are logged in the file RFNM
*/
/* Some of the HTML utilities were written by Rob McCool */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define BKGROUND "http://fivedots.coe.psu.ac.th/~ad/chalk.jpg"
#define MAX_ENTRIES 5           /* max number of input fields */
#define MAXLEN 300              /* max length of a string */
#define NOVAL "$$no-value$$"    /* when no value is found */

#define PATNO 5         /* max number of patterns  */
#define RESNO 10        /* max number of matching lines */

#define FNM "/export/home/ad/public_html/code/sdb/dir.alp"    
                                    /* 'database' file to be searched */
#define RFNM  "/export/home/ad/public_html/code/sdb/people.txt"   
                                    /* log file */

typedef struct {         /* for form nameString=valueString pairs */
    char *name;
    char *val;
} entry;


int get_pats(char *pats[], entry entries[], int etnum);
void print_pats(char *pats[], int pno);
void process_pats(char *pats[], int pno);
void build_pipe(char *pats[], int pno, char pipe[]);
void print_matches(char *gpnm);
int num_matches(char *gpnm);

/* Logging */
void record_details(void);

/* Standard Form Functions */
void start_reply(char *title);
void cgi_errs();
int build_entries(entry entries[]);
char *find_value(entry entries[], int etnum, char *name);

/* Debugging Utilities */
int input_entries(entry entries[]);
void show_entries(entry entries[], int etnum);

/* CGI utilities written by Rob McCool */
char *makeword(char *line, char stop);
char *fmakeword(FILE *f, char stop, int *len);
void unescape_url(char *url);
char x2c(char *what);
void plustospace(char *str);


int main() 
/* Extract the string patterns entered in the text entry
   fields of the form, and use them to search the file FNM
   inside process_pats()
*/
{
  entry entries[MAX_ENTRIES];
  char *patterns[PATNO];   /* for the user's search strings */
  int etnum = 0, patno;

  start_reply("Query Result");

  cgi_errs();
  etnum = build_entries(entries);
  record_details();            /* log the user */
/*
  etnum = input_entries(entries);
  show_entries(entries, etnum);
*/
  patno = get_pats(patterns, entries, etnum);
  if (patno > 0) {
    print_pats(patterns, patno);
    process_pats(patterns, patno);
  }
  else
    printf("Must specify at least 1 pattern<p>\n");

  printf("</body></html>\n");
  return 0;
}



int get_pats(char *pats[], entry entries[], int etnum)
/* Extract the text strings and store them in the pats array. */
{
  int patno = 0, x;

  for (x=0; x < etnum; x++) {
    if (entries[x].val[0] != '\0') {
      pats[patno] =
         (char *)malloc((strlen(entries[x].val)+1)*sizeof(char));
      strcpy(pats[patno], entries[x].val);
      patno++;
    }
  }
  return patno;
}


void print_pats(char *pats[], int pno)
/* Print the string patterns as an unnumbered HTML list */
{
  int x;

  printf("<p>Only a maximum of %d matching lines are shown for any search.</p>",
                             RESNO);
  printf("<p>The following strings are being used for the search:</p>\n");
  printf("<ul>\n");
  for(x=0; x < pno; x++)
    printf("<li> %s\n",pats[x]);
  printf("</ul>\n");
}


void process_pats(char *pats[], int pno)
/* Use fgrep to search the file FNM looking for the
   string patterns. Store the result of the search
   in the temporary file, gpnm, and print it out.
   Use wc to get the number of matches.

   The fgrep string has the form:
     "fgrep 'pats[0]' FNM | fgrep 'pats[1]' | fgrep 'pats[2]' |... > gpnm"
*/
{
  char gpnm[MAXLEN];         /* temporary name for fgrep results file */
  char pipe_expr[MAXLEN];    /* pipe part of fgrep string */
  char gcmd[MAXLEN];         /* complete fgrep string */
  int nlines;

  tmpnam(gpnm);
  build_pipe(pats, pno, pipe_expr);
  sprintf(gcmd,"fgrep '%s' %s %s > %s",
                      pats[0], FNM, pipe_expr, gpnm);
  system(gcmd);
  print_matches(gpnm);

  nlines = num_matches(gpnm);   /* number of matching lines */
  remove(gpnm);

  if (nlines > RESNO)
    printf("<b>%d lines printed from a total of %d.</b><P>", RESNO, nlines);
  else if (nlines == 0)
    printf("<b>No matching lines.</b><P>");
  else
    printf("<b>%d line(s) printed.</b><P>", nlines);
}



void build_pipe(char *pats[], int pno, char pipe[])
/* Construct the pipe part of the fgrep string:
      "| fgrep 'pats[1]' | fgrep 'pats[2]' |..."
*/
{
  char part[MAXLEN];
  int x;

  pipe[0] = '\0';
  for (x=1; x < pno; x++) {
    sprintf(part, "| fgrep '%s' ", pats[x]);
    strcat(pipe, part);
  }
}


void print_matches(char *gpnm)
/* Print the lines in the fgrep results file, gpnm, as an
   unnumbered HTML list. At most the first RESNO are printed.
*/
{
  char result[MAXLEN];    /* a results line */
  FILE *fp;
  int no = 0;

  printf("<b>The lines found are:</b><P>");
  printf("<ul>\n");
  fp = fopen(gpnm,"r");
  while ((no < RESNO) && (fgets(result, MAXLEN, fp) != NULL)) {
    printf("<li> %s<P>\n", result); 
    no++;
  }
  printf("</ul>\n");
  fclose(fp);
}


int num_matches(char *gpnm)
/* Count the total number of matching lines 
   by applying the UNIX command "wc" to the
   fgrep results file, gpnm.
*/
{
  char wcmd[MAXLEN];    /* for the wc string */
  FILE* fp;
  int nlines;

  sprintf(wcmd, "wc -l %s", gpnm);
  fp = popen(wcmd, "r");
  fscanf(fp,"%d", &nlines);   /* read first digit of wc output */
  fclose(fp);

  return nlines;
}


/* Logging */

void record_details(void)
/* Log details about the user in the file RFNM.
   Store the values in the REMOTE_USER, REMOTE_IDENT, 
   REMOTE_HOST, and REMOTE_ADDR environment variables, and
   the time.
*/
{
  char *ruser, *rid, *rhost, *raddr;
  struct tm *tp;
  time_t now;
  FILE *rfp;

  rfp = fopen(RFNM,"a");

  if ((ruser = getenv("REMOTE_USER")) == NULL)
    fprintf(rfp,"no_ruser  ");
  else
    fprintf(rfp, "%s  ",ruser);

  if ((rid = getenv("REMOTE_IDENT")) == NULL)
    fprintf(rfp,"no_rid  ");
  else
    fprintf(rfp, "%s  ",rid);

  if ((rhost = getenv("REMOTE_HOST")) == NULL)
    fprintf(rfp,"no_rhost  ");
  else
    fprintf(rfp, "%s  ",rhost);

  if ((raddr = getenv("REMOTE_ADDR")) == NULL)
    fprintf(rfp,"no_raddr  ");
  else
    fprintf(rfp, "%s  ",raddr);

  now = time(NULL);
  tp = localtime(&now);
  if (tp == NULL)
    fprintf(rfp,"no_ltime\n");
  else
    fprintf(rfp, "%s",ctime(&now));

  fclose(rfp);
}



/* Standard Form Functions */

void start_reply(char *title)
/* Output the HTML header, including the title and H1 container */
{
  printf("Content-type: text/html\n\n");
  printf("<html><head><title>%s</title></head>\n", title);
  printf("<body background=\"%s\">", BKGROUND);
  printf("<H1 align=center>%s</H1>\n", title);
  fflush(NULL);   /* force output to occur now */
}


void cgi_errs()
/* Check for errors in the REQUEST_METHOD and CONTENT_TYPE
   environment variables.
*/
{
  if(strcmp(getenv("REQUEST_METHOD"),"POST")) {
    printf("<P>This script should be referenced with a METHOD of POST.\n");
    printf("If you don't understand this, read ");
    printf("<A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs/fill-out-forms/overview.html\">forms overview</A>.</P>\n");
    printf("</body></html>\n");
    exit(1);
  }
  if(strcmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) {
    printf("<P>This script can only be used to decode form results.</P>\n");
    printf("</body></html>\n");
    exit(1);
  }
}


int build_entries(entry entries[])
/* Fill in the entries array with nameString=valueString 
   pairs from the form. 
*/
{
  int len, x;

  len = atoi(getenv("CONTENT_LENGTH"));
  for(x=0; len && (!feof(stdin)) && (x < MAX_ENTRIES); x++) {
    entries[x].val = fmakeword(stdin,'&',&len);
    plustospace(entries[x].val);
    unescape_url(entries[x].val);
    entries[x].name = makeword(entries[x].val,'=');
  }
  return x;  
}


char *find_value(entry entries[], int etnum, char *name)
/* Search through the entries array for a name string 
   which matches name and return its associated value 
   string. Otherwise return NOVAL.
*/
{
  int x;
  char *value;

  for(x=0; x < etnum; x++)
    if (strcmp(entries[x].name, name) == 0) {
      value = (char *)malloc(sizeof(char)*(strlen(entries[x].val)+1));
      strcpy(value, entries[x].val);
      break;
    }
  if ((x == etnum) || (value[0] == '\0')) {
    value = (char *)malloc(sizeof(char)*(strlen(NOVAL)+1));
    strcpy(value, NOVAL);
  }
  return value;
}



/* Debugging Utilities */

int input_entries(entry entries[])
/* Fill in the entries array with nameString=valueString pairs 
   entered by the user at the keyboard.
   Each pair should be entered in the format:
      nameString=valueString
   Terminate the input with <ctrl>d
*/
{
  int num = 0, namelen, linelen;
  char line[MAXLEN];

  printf("\nEnter name=value pairs:\n\n");

  while (fgets(line, MAXLEN, stdin) && (num < MAX_ENTRIES)) {
    linelen = strlen(line);
    namelen = strcspn(line, "=");
    entries[num].name =
      (char *)malloc(sizeof(char)*(namelen+1));
    strncpy(entries[num].name, line, namelen);
    entries[num].name[namelen] = '\0';
    entries[num].val =
      (char *)malloc(sizeof(char)*(linelen-namelen));
    strcpy(entries[num].val, &line[namelen+1]);
    num++;
  }
  return num;
}


void show_entries(entry entries[], int etnum)
/* Output the nameString=valueString pairs as an HTML 
   unnumbered list */
{
  int x;

  printf("<P>You submitted these nameString=valueString pairs:</P>\n");
  printf("<ul>\n");
  for(x=0; x < etnum; x++)
    printf("<li><code>%s = %s</code>\n", entries[x].name, entries[x].val);
  printf("</ul>\n");
}



/* CGI Utilities by Rob McCool; 
   comments by Andrew Davison */

char *makeword(char *line, char stop) 
/* Builds a word by extracting characters from line up to the
   stopping character in stop (or until line is exhausted).
*/
{
  int x = 0, y = 0;
  char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));

  for(x=0; ((line[x]) && (line[x] != stop)); x++)
     word[x] = line[x];
  word[x] = '\0';
  if(line[x]) 
    ++x;
  while((line[y++] = line[x++]));
  return word;
}


char *fmakeword(FILE *f, char stop, int *len) 
/* Similar to makeword(): builds a word by extracting characters 
   from the file f up to the stopping character in stop (or
   until f is exhausted). The function is also supplied with
   the length of the string (len) left unread in f.
*/
{
  int wsize = 102400, ll = 0;
  char *word = (char *) malloc(sizeof(char) * (wsize + 1));

  while(1) {
    word[ll] = (char)fgetc(f);
    if(ll == wsize) {
       word[ll+1] = '\0';
       wsize += 102400;
       word = (char *)realloc(word,sizeof(char)*(wsize+1));
    }
    --(*len);
    if((word[ll] == stop) || (feof(f)) || (!(*len))) {
       if(word[ll] != stop) ll++;
       word[ll] = '\0';
       return word;
    }
    ++ll;
  }
}


void unescape_url(char *url) 
/* Convert the hexadecimal characters in url into ASCII.
   They are detected by starting with a '%'.
*/
{
  register int x, y;

  for(x=0,y=0; url[y]; ++x,++y) {
    if((url[x] = url[y]) == '%') {
      url[x] = x2c(&url[y+1]);
      y+=2;
    }
  }
  url[x] = '\0';
}


char x2c(char *what) 
/* Convert the hexadecimal (consisting of two characters) into
   a single ASCII character.
*/
{
  register char digit;

  digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
  digit *= 16;
  digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
  return(digit);
}


void plustospace(char *str) 
/* Replace '+'s by spaces in str */
{
  register int x;

  for(x=0; str[x]; x++) 
    if(str[x] == '+') str[x] = ' ';
}


