#ifndef CGI_H
#define CGI_H

/* ===========================================
 * A class to encapsulate the CGI decoding
 * proccess for the GET and POST methods.  The 
 * class also encapsulates a generic container
 * used to hold the decoded form data.
 *
 * (C) 1998 Matthew W. Coan
 *          mcoan@slack.net
 *          http://slack.net/~mcoan
 *
 *       You are free to use this,
 *       just as long as the above
 *       copywright info. stays 
 *       intact and. Any changes 
 *       made must be reported
 *       to me.  Help me make this
 *       the best C++ CGI system
 *       out there.
 */

#include <string>
#include <map>
#include <list>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <iostream>

namespace cgi_tools {

/** 
 * Default container.
 */
typedef std::map< std::string, std::string > cgi_container;

/**
 * The cgi class parses and decodes the form data 
 * storing it in the char* to char* associative container.
 */
template< class container = cgi_container >
class cgi {
public:
   enum { MAX_CONTENT = 1024 * 1024 };
private:
   container form_map;
   size_t max_content;
   std::string urldecode(const std::string & str) {
      std::string ret;
      std::string temp;
      for(int i = 0; i < str.size(); i++) {
         if(str[i] == '%') {
            i++;
            if(i < str.size()) {
               temp = str.substr(i,2);
               std::stringstream ss;
               ss << std::hex << temp;
               int ch;
               ss >> ch;
               ret += (char)ch;
               i++;
            }
         }
         else {
            ret += (char)str[i];
         }
      }
      for(int i = 0; i < ret.size(); i++) {
         if(ret[i] == '+') {
            ret[i] = ' ';
         }
      }
      return ret;
   }
   void fill_map(char * buffer) {
      std::list< std::string > lis;
      char * ptr = strtok(buffer, "&");
      while(ptr != NULL) {
         lis.push_back(ptr);
         ptr = strtok(NULL, "&");
      }
      std::string::size_type index;
      std::string key,value;
      for(std::list< std::string >::iterator p = lis.begin(); p != lis.end(); p++) {
         std::string & ref = *p;
         index = ref.find('=');
         if(index != std::string::npos) {
            key = urldecode(ref.substr(0,index));
            value = urldecode(ref.substr(index+1));
         }
         else {
            key = urldecode(ref);
            value = "";
         }
         form_map[key] = value;
      }
   }
   void filter(char * buffer, int size) {
      int count = 0;
      for(char * ptr = buffer; count < size; ptr++, count++) {
          if(isalnum(*ptr)
             || ispunct(*ptr) 
             || isspace(*ptr)) {
          }
          else {
             *ptr = '.';
          }
      }
   }
public:
   cgi(size_t max_size = MAX_CONTENT) {
      const char * method = getenv("REQUEST_METHOD");
      char * buffer = 0;
      if(method != 0) {
         if(strcmp(method, "GET") == 0) {
            buffer = getenv("QUERY_STRING");
            if(buffer) {
               int size = strlen(buffer);
               if(size >= max_content) {
                  std::cout << "CGI max content...";
                  exit(0);
               }
               char * temp = new char [ size + 1 ];
               memset(temp,0,size+1);
               memcpy(temp,buffer,size);
               buffer = temp;
               filter(buffer,size);
               fill_map(buffer);
               delete [] buffer;
            }
            else {
               std::cout << "Bad CGI request...";
               exit(0);
            }
         }
         else if(strcmp(method, "POST") == 0) {
            const char * content_length = getenv("CONTENT_LENGTH");
            if(content_length) {
               int size = atoi(content_length);
               if(size >= 0 && size >= max_content) {
                  std::cout << "CGI max content...";
                  exit(0);
               }
               buffer = new char [ size + 1 ];
               memset(buffer, 0, size+1);
               std::cin.read(buffer, size);
               filter(buffer,size);
               fill_map(buffer);
               delete [] buffer;
            }
            else {
               std::cout << "Bad CGI request...";
               exit(0);
            }
         }
         else {
            std::cout << "must run as a cgi..." << std::endl;
            exit(0);
         }
      }
   }

   ~cgi() {
   }

   /**
    * Subscripts are overloaded for lookup.
    */
   const char * operator[](const std::string & n) {
      typename container::iterator ptr = form_map.find(n);
      const char * ret = 0;
      if(ptr != form_map.end()) {
         ret = ptr->second.c_str();
      }
      return ret;
   } 

   /**
    * Return the form map container.
    */
   container & get_container() { return form_map; }   
};

typedef cgi< > CGI;

}

#endif /* End of CGI_H. */


