/*
BOIL (Brunni's Own Interpreter Language)
(c) 1998 netEstate (http://www.netestate.de/, info@netestate.de),
Michael Brunnbauer & Markus Hendel, Gesellschaft fuer Netzwerkkommunikation bR
Am Bluetenanger 8, 80995 Muenchen, Germany

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
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "general.h"
#include "handles.cc"
#include "boil.h"
#include "boil.tab.h"
#include "libs.h"

struct mimetype
{
  char* ext;
  char* type;
};
 
mimetype mimetypes[]=
{
  { "gif","image/gif" },
  { "jpg","image/jpeg" },
  { "jpeg","image/jpeg" },
  { "jpe","image/jpeg" },
  { "html","text/html" },
  { "htm","text/html" },
  { "zip","application/zip" },
  { "xls","application/vnd.ms-excel" },
  { "","application/octet-stream" },
  { 0,"application/octet-stream" }
};

char* cgiext[]=
{
  "cgi",0
};

int args_cgiwrap[]= { TYPE_VALUE_STRING,
                      TYPE_VALUE_STRING,
                      TYPE_VALUE_ULONG,
                      0 };

flowctrl func_cgiwrap(type_block* aktblock,opt* options,
                      joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_ULONG;
  back.returnval.val.ulongval=0;
  char buf[CGIWRAP_BUF];

  struct stat filestat;

  if (!defined((*args)[0].stringval) ||
      !defined((*args)[1].stringval))
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: string not defined");
      return back;
    };

  unsigned long nohtmlheader=(*args)[2].val.ulongval;
  string file=(*args)[0].stringval;

  if (strstr(file,".."))
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: security violation '..' in "+file);
      return back;
    };

  if (stat(file,&filestat)==-1)
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: cannot stat "+file);
      return back;
    };
  if (S_ISDIR(filestat.st_mode))
    {
      if (!stat(file+"/index.html",&filestat))
        {
          file+="/index.html";
        }
      else if (!stat(file+"/index.htm",&filestat))
        {
          file+="/index.htm";
        }
      else
        {
          setvarulong("error",1);
          setvarstring("perror","cgiwrap: index.htm[l] not found: "+file);
          return back;
        };
    }
  else if (!S_ISREG(filestat.st_mode) && !S_ISLNK(filestat.st_mode))
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: special file: "+file);
      return back;
    };

  unsigned long i;
  char* empty=".";
  char* extension=strrchr(file,(int) '.');
  if (!extension)
    {
      extension=empty;
    };
  extension++;
  string extension1=extension;
  str_lwr(extension1);

  /* cgi-skript ? */
  for (i=0;cgiext[i];i++)
    {
      if (!strcmp(cgiext[i],extension1))
        {
          char* dummy=0;char** argv=&dummy;
          if ((*args)[1].stringval!="")
            {
              if (setenv("REQUEST_METHOD","POST",1)==-1)
                {
                  setvarulong("error",1);
                  setvarstring("perror",
                               "cgiwrap: cannot setenv(REQUEST_METHOD)");
                  return back;
                };
            };
          FILE* prg=myexec(file,argv);
          if (!prg)
            {
              setvarulong("error",1);
              setvarstring("perror",
                           "cgiwrap: cannot exec "+(*args)[0].stringval);
              return back;
            };
          if ((*args)[1].stringval!="")
            {
              if (fwrite((*args)[1].stringval,1,strlen((*args)[1].stringval),
                         prg)!=strlen((*args)[1].stringval))
                {
                  setvarulong("error",1);
                  setvarstring("perror","cgiwrap: could not write postdata");
                  return back;
                };
            };
          if (fclose(prg))
            {
              setvarulong("error",1);
              setvarstring("perror","cgiwrap: returncode of fclose()!=0");
              return back;
            };
          back.returnval.val.ulongval=1;
          return back;
        };
    };

  /* search content type */
  string ctype=0;
  for (i=0;mimetypes[i].ext;i++)
    {
      if (!strcmp(mimetypes[i].ext,extension1))
        {
          ctype=mimetypes[i].type;
          break;
        };
    };
  if (!defined(ctype)) { ctype=mimetypes[i].type; };
  
  int handle=open(file,O_RDONLY);
  if (handle==-1)
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: cannot open "+file);
      return back;
    };
  
  string head="Content-type: "+ctype+"\n\n";

  if (head=="Content-type: text/html\n\n" && nohtmlheader) { head="\n"; };

  if ((unsigned long) write(1,head,strlen(head))!=
      (unsigned long) strlen(head))
    {
      setvarulong("error",1);
      setvarstring("perror","cgiwrap: output of header failed");
      close(handle);
      return back;
    };
  
  size_t size;
  while ((size=read(handle,buf,CGIWRAP_BUF))>0)
    {
      if ((unsigned long) write(1,buf,size)!=
          (unsigned long) size)
        {
          setvarulong("error",1);
          setvarstring("perror","cgiwrap: output of "+file+" failed");
          close(handle);
          return back;
        };
    };
  close(handle);
  back.returnval.val.ulongval=1;
  return back;
}

int args_getpost[]= { 0 };

flowctrl func_getpost(type_block* aktblock,opt* options,
                      joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back,call,call1;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_ULONG;
  back.returnval.val.ulongval=0;

  char buf[MAX_LINE_SIZE];
  string postdata="";
  char* ptr;
  size_t size;
  int status;
  type_value var;
  type_value* varptr;
  var.type=TYPE_VALUE_STRING;
  hashdir<char,string,256,0> declared;
  bool found;

  if (!getenv("REQUEST_METHOD"))
    {
      setvarulong("error",1);
      setvarstring("perror",
                   "getpost: environment-variable REQUEST_METHOD not defined");
      return back;
    };

  if (strcmp(getenv("REQUEST_METHOD"),"POST"))
    {
      setvarulong("error",1);
      setvarstring("perror","getpost: request-method is not POST");
      return back;
    };

  while ((size=fread(buf,1,MAX_LINE_SIZE-1,stdin))==MAX_LINE_SIZE-1)
    {
      buf[size]=0;
      postdata+=buf;
    };
  if (size) { buf[size]=0; postdata+=buf; };

  ptr=postdata.zgr;
  
  back.returnval.val.ulongval=1;
  string name;
  while ((status=urlencode_parse(&ptr,name,var.stringval))==1)
    {
      call=declarevar(aktblock,options,&var,name);
      if (call.ctrl!=FLOW_OK)
        {
          /* already declared by getpost() ? */
          if (declared.get_listelem(name))
            {
              /* yes -> append value */
              found=1;
              call1=getvar(aktblock,options,varptr,name);
              if (call1.ctrl!=FLOW_OK)
                {
                  setvarulong("error",1);
                  setvarstring("perror",
                               "getpost: hum ? could not get already\
defined variable "+name+":"+call1.error);
                  back.returnval.val.ulongval=0;
                }
              else
                {
                  varptr->stringval+=","+var.stringval;
                };
            }
          else
            {
              setvarulong("error",1);
              setvarstring("perror","getpost: could not declare variable "+
                           name+":"+call.error);
              back.returnval.val.ulongval=0;
            };
        }
      else
        {
          char ch;
          declared.insert_listelem(ch,name);
        };
    };
  
  if (status==-1)
    {
      setvarulong("error",1);
      setvarstring("perror","getpost: syntax error in post-data");
      back.returnval.val.ulongval=0;
    }
  else
    {
      call=declarevar(aktblock,options,&var,name);
      if (call.ctrl!=FLOW_OK)
        {
          setvarulong("error",1);
          setvarstring("perror",
                     "getpost: could not declare variable "+name+":"+
                       call.error);
          back.returnval.val.ulongval=0;
        };
    };
  
  return back;
}

int args_postdata[]= { TYPE_VALUE_STRING,
                       0 };

flowctrl func_postdata(type_block* aktblock,opt* options,
                       joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back,call,call1;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_ULONG;
  back.returnval.val.ulongval=0;
  char* ptr;
  int status;
  type_value var;
  type_value* varptr;
  var.type=TYPE_VALUE_STRING;
  hashdir<char,string,256,0> declared;
  bool found;

  if (!defined((*args)[0].stringval))
    {
      setvarulong("error",1);
      setvarstring("perror","postdata: string not defined");
      return back;
    };

  ptr=(*args)[0].stringval;
  
  back.returnval.val.ulongval=1;
  string name;
  while ((status=urlencode_parse(&ptr,name,var.stringval))==1)
    {
      call=declarevar(aktblock,options,&var,name);
      if (call.ctrl!=FLOW_OK)
        {
          if (declared.get_listelem(name))
            {
              /* yes -> append value */
              found=1;
              call1=getvar(aktblock,options,varptr,name);
                  if (call1.ctrl!=FLOW_OK)
                    {
                      setvarulong("error",1);
                      setvarstring("perror",
                                   "postdata: hum ? could not get already\
defined variable "+name+":"+call1.error);
                      back.returnval.val.ulongval=0;
                    }
                  else
                    {
                      varptr->stringval+=","+var.stringval;
                    };
            }
          else
            {
              setvarulong("error",1);
              setvarstring("perror","postdata: could not declare variable "+
                           name+":"+call.error);
              back.returnval.val.ulongval=0;
            };
        }
      else
        {
          char ch;
          declared.insert_listelem(ch,name);
        };
    };
  
  if (status==-1)
    {
      setvarulong("error",1);
      setvarstring("perror","postdata: syntax error in post-data");
      back.returnval.val.ulongval=0;
    }
  else
    {
      call=declarevar(aktblock,options,&var,name);
      if (call.ctrl!=FLOW_OK)
        {
          setvarulong("error",1);
          setvarstring("perror",
                       "postdata:could not declare variable "+name+":"+
                       call.error);
          back.returnval.val.ulongval=0;
        };
    };

  return back;
}

int args_file_upload[] = { TYPE_VALUE_LONG,
                           0 };

flowctrl func_file_upload(type_block* aktblock,opt* options,
                          joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_STRING;
  back.returnval.stringval=0;

  char buf[MAX_LINE_SIZE];
  char buf1[MAX_LINE_SIZE];
  string separator,token;
  long maxsize=(*args)[0].val.longval;
  if (maxsize<0)
  {
    setvarulong("error",1);
    setvarstring("perror","file_upload: maxsize < 0");
    return back;
  };

  if (!fgets(buf,MAX_LINE_SIZE,stdin))
    {
      setvarulong("error",1);
      setvarstring("perror","file_upload: unexpected EOF");
      return back;
    };
  str_lfret(buf);
  separator=buf;
  string tmpfile;

  while (buf!=separator+"--\r\n")
    {
      if (!fgets(buf,MAX_LINE_SIZE,stdin))
        {
          setvarulong("error",1);
          setvarstring("perror","file_upload: unexpected EOF");
          return back;
        };
      char* ptr=buf;
      string content_disposition=strsep(&ptr,";\n");
      string name=strsep(&ptr,";\n");
      string filename=strsep(&ptr,";\n");
      if (!defined(content_disposition) || !defined(name))
        {
          setvarulong("error",1);
          setvarstring("perror","file_upload: syntax error");
          return back;
        };

      /* ignore line */
      if (!fgets(buf,MAX_LINE_SIZE,stdin))
        {
          setvarulong("error",1);
          setvarstring("perror","file_upload: unexpected EOF");
          return back;
        };

      if (content_disposition!="Content-Disposition: form-data")
        {
          setvarulong("error",1);
          setvarstring("perror","file_upload: unknown disposition:"+
                       content_disposition);
          return back;
        };
      name=(string) (strchr(name,'"')+1);
      strchr(name,'"')[0]=0;
      name.len=strlen(name);

      if (defined(filename) && strstr(filename.zgr," filename=")==filename.zgr)
        {
          /* a file is transmitted */
 
          /* ignore line */
          if (!fgets(buf,MAX_LINE_SIZE,stdin))
          { 
           setvarulong("error",1);
           setvarstring("perror","file_upload: unexpected EOF");
           return back;
          };
 
          unsigned long size=0;
          tmpfile=tmpnam(NULL);
          if (!defined(tmpfile))
            {
              setvarulong("error",1);
              setvarstring("perror","file_upload: tmpnam() failed");
              return back;
            };
          
          int handle;
          handle=open(tmpfile,O_WRONLY|O_CREAT,
                      S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
          if (handle==-1)
            {
              setvarulong("error",1);
              setvarstring("perror","file_upload: cannot open "+tmpfile);
              return back;
            };
          long i;
          
          for (i=0;i!=MAX_LINE_SIZE;i++) { buf[i]=0; };
          if (!fgets(buf,MAX_LINE_SIZE,stdin))
            {
              setvarulong("error",1);
              setvarstring("perror","file_upload: unexpected EOF");
              return back;
            };
          while (buf!=separator+"\r\n" && buf!=separator+"--\r\n")
            {
              memcpy(buf1,buf,(size_t) MAX_LINE_SIZE);
              for (i=0;i!=MAX_LINE_SIZE;i++) { buf[i]=0; };
              if (!fgets(buf,MAX_LINE_SIZE,stdin))
                {
                  setvarulong("error",1);
                  setvarstring("perror","file_upload: unexpected EOF");
                  return back;
                };
              long aktsize=MAX_LINE_SIZE-1;
              for (i=MAX_LINE_SIZE-1;i>=0;i--)
                {
                  if (buf1[i])
		    {
		      if (buf1[i]=='\n')
			{
			  aktsize=i+1; break;
			}
		      else
			{
			  aktsize=MAX_LINE_SIZE-1; break;
			};
		    };
                };

              if (buf==separator+"\r\n" || buf==separator+"--\r\n")
                {
                  size+=(aktsize-2);
                  if (size>(unsigned long) maxsize)
                    {
                      setvarulong("error",1);
                      setvarstring("perror","file_upload: file too big");
                      return back;
                    };
                  if (write(handle,buf1,aktsize-2)!=aktsize-2)
                    {
                      setvarulong("error",1);
                      setvarstring("perror","file_upload: write() failed");
                      return back;
                    };
                  if (close(handle)==-1)
                    {
                      setvarulong("error",1);
                      setvarstring("perror","file_upload: close() failed");
                      return back;
                    };
                }
              else
                {
                  size+=aktsize;
                  if (size>(unsigned long) maxsize)
                    {
                      setvarulong("error",1);
                      setvarstring("perror","file_upload: file too big");
                      return back;
                    };
                  if (write(handle,buf1,aktsize)!=aktsize)
                    {
                      setvarulong("error",1);
                      setvarstring("perror","file_upload: write() failed");
                      return back;
                    };
                };
            };
        }
      else
        {
          /* normal form-data */
          string value="";
          if (!fgets(buf,MAX_LINE_SIZE,stdin))
            {
              setvarulong("error",1);
              setvarstring("perror","file_upload: unexpected EOF");
              return back;
            };
          while (buf!=separator+"\r\n" && buf!=separator+"--\r\n")
            {
              memcpy(buf1,buf,(size_t) MAX_LINE_SIZE);
              if (!fgets(buf,MAX_LINE_SIZE,stdin))
                {
                  setvarulong("error",1);
                  setvarstring("perror","file_upload: unexpected EOF");
                  return back;
                };
              if (buf==separator+"\r\n" || buf==separator+"--\r\n")
                {
                  str_lfret(buf1);
                  value=value+buf1;
                  
                  type_value var;
                  var.type=TYPE_VALUE_STRING;
                  var.stringval=value;
                  flowctrl call=declarevar(aktblock,options,&var,name);
                  if (call.ctrl!=FLOW_OK)
                    {
                      setvarulong("error",1);
                      setvarstring("perror",
                                   "file_upload: could not declare variable "+
                                   name+":"+call.error);
                      return back;
                    };
                }
              else
                {
                  value=value+buf1;
                };
            };
        };
    };

  back.returnval.stringval=tmpfile;
  return back;
}

int args_htmlenc[]= { TYPE_VALUE_STRING,
                      0 };

flowctrl func_htmlenc(type_block* aktblock,opt* options,
                      joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_STRING;
  char buf[MAX_LINE_SIZE];

  if (!defined((*args)[0].stringval))
    {
      setvarulong("error",1);
      setvarstring("perror","htmlenc: string not defined");
      return back;
    };

  char* src=(*args)[0].stringval.zgr;
  back.returnval.stringval="";

  for (unsigned long i=0;src[i];i++)
    {
      if (src[i]=='<' || src[i]=='>' || src[i]=='\"' || src[i]=='\\' ||
          src[i]=='&')
        {
          snprintf(buf,MAX_LINE_SIZE,"&#%u;",(unsigned int) src[i]);
        }
      else
        {
          buf[0]=src[i];
          buf[1]=0;
        };

      back.returnval.stringval+=buf;
    };

  return back;
}

int args_phtmlenc[]= { TYPE_VALUE_STRING,
                       0 };

flowctrl func_phtmlenc(type_block* aktblock,opt* options,
                       joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_ULONG;
  back.returnval.val.ulongval=0;
  char buf[MAX_LINE_SIZE];

  if (!defined((*args)[0].stringval))
    {
      setvarulong("error",1);
      setvarstring("perror","phtmlenc: string not defined");
      return back;
    };

  char* src=(*args)[0].stringval.zgr;
  back.returnval.stringval="";

  for (unsigned long i=0;src[i];i++)
    {
      if (src[i]=='<' || src[i]=='>' || src[i]=='\"' || src[i]=='\\' ||
          src[i]=='&')
        {
          snprintf(buf,MAX_LINE_SIZE,"&#%u;",(unsigned int) src[i]);
        }
      else
        {
          buf[0]=src[i];
          buf[1]=0;
        };

      if (fwrite((void*) buf,1,strlen(buf),outstream)!=strlen(buf))
        {
          setvarulong("error",1);
          setvarstring("perror","phtmlenc: fwrite() failed");
          return back;
        };
    };

  back.returnval.val.ulongval=1;
  return back;
}

int args_fullhtmlenc[]= { TYPE_VALUE_STRING,
                          0 };

flowctrl func_fullhtmlenc(type_block* aktblock,opt* options,
                          joined_list<type_value>* args)
{
  aktblock=aktblock;options=options;args=args; /* to prevent warnings */
  flowctrl back;
  back.ctrl=FLOW_RETURN;
  back.returnval.type=TYPE_VALUE_STRING;
  char buf[MAX_LINE_SIZE];

  if (!defined((*args)[0].stringval))
    {
      setvarulong("error",1);
      setvarstring("perror","htmlenc: string not defined");
      return back;
    };

  unsigned char* src=(unsigned char*) (*args)[0].stringval.zgr;
  back.returnval.stringval="";

  for (unsigned long i=0;src[i];i++)
    {
      if ((src[i]>=48 && src[i]<=57) ||
          (src[i]>=65 && src[i]<=90) ||
          (src[i]>=97 && src[i]<=122) ||
          src[i]==46)
        {
          buf[0]=src[i];
          buf[1]=0;
        }
      else
        {
          snprintf(buf,MAX_LINE_SIZE,"%%%02X",(unsigned int) src[i]);
        };
      back.returnval.stringval+=buf;
    };

  return back;
}

