/*+++++++++++++++++
  cgi.c - cgi support functions
  markus@mhoenicka.de 7-3-01
  $Id: cgi.c,v 1.6.2.1 2005/11/08 21:39:50 mhoenicka Exp $

   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; either version 2 of the License, or
   (at your option) any later version.
   
   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, see <http://www.gnu.org/licenses/>

   ++++++++++++++++++++++++*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "refdb.h"
#include "linklist.h"
#include "cgi.h"

extern int n_cgi;

/* this is the easiest way to add portability to Cygwin */
/* O_BINARY is defined in fcntl.h on Win32/Cygwin */
#ifndef O_BINARY
#define O_BINARY 0
#endif

/* forward declarations of local functions */
static void unescape_url(char* url);
static char x2c(char* what);

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_cgi(): determines whether the process runs as a CGI app and sets
            a few variables if so

  int is_cgi returns 1 if the process runs as a CGI app with the 
             appropriate environment variables and values, 0 if not

  char* request_method ptr to string that receives the value of the
             environment variable REQUEST_METHOD

  char* content_length ptr to string that receives the value of the
             environment variable CONTENT_LENGTH

  char* delayed_cgi_errmsg ptr to string that receives an error
             message

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int is_cgi(char** request_method, char** content_length, char* delayed_cgi_errmsg) {
  if ((*request_method = getenv("REQUEST_METHOD")) != NULL) {
    if (strcmp(*request_method, "POST") != 0) {
      printf("Content-type: text/plain\n\nIncorrect request method\n");
      strcpy(delayed_cgi_errmsg, "Incorrect request method");
      return 0;
    }
    else if ((*content_length = getenv("CONTENT_LENGTH")) == NULL) {
      printf("Content-type: text/plain\n\nUnknown request size\n");
      strcpy(delayed_cgi_errmsg, "Unknown request size");
      return 0;
    }
    return 1; /* both variables seem to make sense */
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  write_err(): writes a simple error message

  void write_err has no return value

  char* message ptr to string with message to print

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void write_err(char* message) {
  if (!n_cgi) {
    fprintf(stderr, "%s", message);
  }
  else { /* send cgi type string */
    printf("Content-type: text/plain\n\n");
    printf("%s", message);
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  cgi_header(): writes a cgi header

  void cgi_header has no return value

  int type 0 = text/plain, 1 = text/html

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void cgi_header(int type) {
  if (n_cgi) {
    if (!type) {
      printf("Content-type: text/plain\n\n");
    }
    else {
      printf("Content-type: text/html\n\n");
    }
  }
  /* do nothing if not cgi output */
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  decode_cgi(): decodes a cgi string sent by the POST method

  int decode_cgi returns 0 if ok, 1 if mem error, 2 if decoding error

  char* cgi_data string containing the encoded cgi data

  struct liliform* ptr_first ptr to the sentinel of the linked list
                   that receives the name/value pairs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int decode_cgi(char* cgi_data, struct liliform* ptr_first) {
  char* temp_data;
  int name_offset;
  int the_end = 0;
  char* temp_name;
  char* temp_value;

  temp_data = cgi_data;

  while (*temp_data) {
    /* new name/value pair starts here. Look for the name part */
    name_offset = 0;
    temp_name = temp_data;

    while (*temp_data && *temp_data != '=' && name_offset < 32) {
      if (*temp_data == '+') {
	*temp_data = ' ';
      }
      temp_data++;
      name_offset++;
    }

    if (*temp_data != '=') { /* could not find name */
      return 2;
    }

    /* terminate, so temp_name is a valid C string */
    *temp_data = '\0';

    temp_data++; /* skip \0 */
    temp_value = temp_data; /* value starts here */

    /* look for the value part */
    while (*temp_data && *temp_data != '&') {
      if (*temp_data == '+') {
	*temp_data = ' ';
      }
      temp_data++;
    }

    if (*temp_data == '&') {
      *temp_data = '\0';
    }
    else { /* end of string */
      the_end++;
    }

    /* decode %XX values */
    unescape_url(temp_name);
    unescape_url(temp_value);

    if (insert_liliform(ptr_first, temp_name, temp_value)) {
      return 1;
    }

    if (the_end) { /* end of data */
      return 0;
    }
    temp_data++;
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  load_html(): loads a string from a html template

  char* load_html returns a ptr to the loaded string, NULL if failed

  char* basename ptr to string with the basename (no extension, no path)
                   of the file containing the html fragment

  char* sharedir ptr to string with the location of the shareable
                 RefDB files. If this string is empty or NULL, the
		 fn uses the environment variable REFDBLIB

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* load_html(char* basename, char* sharedir) {
  int infile_fd;
  int n_done = 0;
  size_t read_result;
  size_t bytes_read;
  size_t html_frag_size;
  char* html_frag;
  char* new_html_frag;
  char full_name[_POSIX_PATH_MAX];
  char* refdblib = NULL; /* value of the REFDBLIB environment variable */
  char default_refdblib[_POSIX_PATH_MAX] = SYSCONFDIR; /* default if REFDBLIB is not set */

  /* assemble the full name of the file */
  if (sharedir && *sharedir) {
    refdblib = sharedir;
  }
  else {
    refdblib = getenv("REFDBLIB");
  }

  if (!refdblib) { /* use a default location if the environment variable is
		      not set, e.g. in a daemon process */
    refdblib = default_refdblib;
  }

  strcpy(full_name, refdblib);
  if (full_name[strlen(full_name)-1] != '/') {
    strcat(full_name, "/");
  }
  strcat(full_name, "templates/");
  strcat(full_name, basename);
  strcat(full_name, HTML_FRAG_EXT);

/*    fprintf(stderr, "%s<<\n", full_name); */

  /* open the input file */
  infile_fd = open(full_name, O_RDONLY|O_BINARY);

  if (infile_fd == -1) { /* open failed */
    return NULL;
  }

  /* allocate a chunk of memory to load the html fragment */
  html_frag = (char*)malloc(HTML_FRAG_SIZE);

  if (html_frag == NULL) {
    close(infile_fd);
    return NULL;
  }

  bytes_read = 0;
  html_frag_size = HTML_FRAG_SIZE;

  while (!n_done) {
    read_result = read(infile_fd, (void*)(html_frag+bytes_read), HTML_FRAG_SIZE);
    if (read_result < HTML_FRAG_SIZE) {
      n_done++;
    }
    else {
      html_frag_size += HTML_FRAG_SIZE;
      new_html_frag = (char*)realloc(html_frag, html_frag_size);
      if (new_html_frag == NULL) {
	close(infile_fd);
	free(html_frag);
	return NULL;
      }
      html_frag = new_html_frag;
    }
    if (read_result != -1) {
      bytes_read += read_result;
    }
  }

  close(infile_fd);

  if (read_result == -1) {
    free(html_frag);
    return NULL;
  }

  html_frag[bytes_read] = '\0'; /* terminate to get a C style string */

  return html_frag;
}


/* The following routines were taken (almost) literally from
   Matthew&Stones, Beginning Linux Programming, WROX 1996 */

/* this routine borrowed from the examples that come with the NCSA server */
static void unescape_url(char* url) {
  register signed int x,y;
  for (x = 0, y = 0; url[y]; ++x,++y) {
    if ((url[x] = url[y]) == '%') {
      if ((url[x] = x2c(&url[y+1])) == 0x0D) { /* eliminate \r sent by */
	x--;                                   /* Windoze browsers */
      }
      y += 2;
    }
  }
  url[x] = '\0';
}

/* this routine borrowed from the examples that come with the NCSA server */
static char x2c(char* what) {
  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;
}







