#include <unistd.h>
#include <string>
#include <cctype>
#include <errno.h>
#include <iostream>
#include <cstdlib>
#include "mitm.h"
#include "ip-utils.h"


typedef std::pair<std::string, std::string> spair;
typedef const char * ccp;


// Deja vu c++ style.

   static ccp skip_space(ccp, ccp);
   static std::string receive(int inf, resource &, unsigned = 0);
   static std::string read_request(int, resource &);
   static std::string get_uri(ccp, unsigned);
   static bool request_complete(ccp, size_t);


static std::string
do_options(int argc, char * argv[]) {

  // Figure out the command-line options, of which there should be only one.

  std::string port = "10305";

  int c;
  extern char *optarg;
  extern int optind;

  while ((c = getopt(argc, argv, "p:")) != EOF)
     switch (c) {
       case 'p': {
	  port = optarg;
	  const int p = atoi(optarg);
	  const int mp = (2 << 15) - 1;
	  if ((p < 1024) || (p > mp)) {
	    std::cerr << "A port should be a number in the range 1024.."
		      << mp << ", not \"" << optarg << "\".\n";
	    exit(EXIT_FAILURE);
	    }
	  break;
	 }

       case '?':
	 std::cerr << "Command format is \"" << argv[0] << " [-pn]\".\n";
	 exit(EXIT_FAILURE);
       }

  if (optind != argc) {
    std::cerr << "\"" << argv[optind] 
	      << "\" is an unrecognized command-line argument.\n"
	      << "Command format is \"" << argv[0] << " [-pn]\".\n";
    exit(EXIT_FAILURE);
    }

  return port;
  }


static spair
extract_server_host(const std::string & uri) {

  // Return the server-host part of the URI delimted by the range [uri_start,
  // uri_end).

  const char * const marker = "://";
  std::string::size_type b = uri.find(marker);
  if (b == std::string::npos) {
    }
  b += strlen(marker);

  std::string::size_type e = uri.find("/", b);
  if (e == std::string::npos)
    e = uri.size();

  std::string::size_type m = uri.find(":", b);
  if (m == std::string::npos)
    m = e;

  spair host = std::make_pair(uri.substr(b, m - b), "");
  host.second = (m == e) ? "80" : (m++, uri.substr(m, e - m));

  return host;
  }


static ccp
has_prefix(ccp prefix, ccp start, ccp end) {

  // If the string delimited by [start, end) begins with the given prefix (case
  // insensitive), return a pointer to the first character after the prefix in
  // the string; otherwise return start unmolested.  (this code does not assume
  // that prefix is non-empty).

  ccp s = start;

  while ((s < end) and (tolower(*prefix) == tolower(*s)) and *prefix) {
    s++;
    prefix++;
    }

  return *prefix ? start : s;
  }


static int
connect(int client, std::string & uri) {

  // Read the message sent by the client over the given socket.

  resource msg = { 0, 0 };
  std::string str = read_request(client, msg);
  if (!str.empty()) {
    std::cerr << "Error message during receive:  " << str << ".\n";
    delete [] msg.data;
    return -1;
    }
  // (std::cerr << "request:\n").write(msg.data, msg.size) << "<<\n";

  uri = get_uri(msg.data, msg.size);
  spair server_host = extract_server_host(uri);
  // std::cerr << "host = " << server_host.first 
  //  	       << ", port = " << server_host.second << "\n";

  const int skt = 
    tcp_connect(server_host.first.c_str(), server_host.second.c_str());

  if (skt > -1)
    write(skt, msg.data, msg.size);

  delete [] msg.data;

  return skt;
  }


static ccp
find_space(ccp start, ccp end) {

  // Return a pointer to the first (leftmost) space character in the interval
  // [start, end); return a pointer to end if no such space character exists.

  while ((start < end) and (not isspace(*start))) start++;

  return start;
  }


static std::string
get_uri(ccp buffer, unsigned bcnt) {

  // Find and return as a string the uri in the http request delimited by
  // [buffer, buffer + bcnt).

  const ccp end = buffer + bcnt;
  ccp uri_start;

  buffer = skip_space(buffer, end);
  uri_start = has_prefix("get", buffer, end);
  if (uri_start == buffer) {
    uri_start = has_prefix("head", buffer, end);
    if (uri_start == buffer) {
      uri_start = has_prefix("post", buffer, end);
      if (uri_start == buffer) {
        }
      }
    }
  uri_start = skip_space(uri_start, end);

  return std::string(uri_start, find_space(uri_start, end));
  }


static std::string
read_request(int inf, resource & document) {

  // 

  static size_t bsize = 10000;
  document.data = new char [bsize];
  document.size = 0;
  
  char * b = document.data;
  size_t bs = bsize;

  do {
    const int e = read(inf, b, bs);
    if (e < 0)
      return "";
    if (e == 0)
      break;
    bs -= e;
    b += e;
    }
  while ((bs > 0) and not request_complete(document.data, bsize - bs));

  document.size = bsize - bs;

  return "";
  }


static std::string
rd(int skt, char * buffer, unsigned & cnt) {

  // Read at most cnt bytes of data from skt and store it in buffer; upon
  // return, cnt contains the number of bytes read.  Return an empty string if
  // everything went ok; otherwise return an informative error message.

  size_t read_amt = cnt;
  while (read_amt > 0) {
    const ssize_t e = read(skt, buffer, read_amt);
    if (e < 0)
      return std::string("server read failed, ") + strerror(errno);
    if (e == 0) 
      break;
    read_amt -= e;
    buffer += e;
    }

  cnt -= read_amt;

  return "";
  }


static std::string
receive(int skt, resource & document, unsigned cnt) {

  // Read the given socket and store the data read in the given array, which
  // should be freed by the caller.

  const size_t bsize = 10000;
  char * buffer = new char [bsize];

  size_t read_amt = bsize;
  std::string emsg = rd(skt, buffer, read_amt);
  if (emsg.empty())
    if (read_amt < bsize) {
      document.size = cnt*bsize + read_amt;
      document.data = new char [document.size];
      memcpy(document.data + cnt*bsize, buffer, read_amt);
      }
    else {
      emsg = receive(skt, document, cnt + 1);
      if (emsg.empty())
	memcpy(document.data + cnt*bsize, buffer, bsize);
      }

  delete [] buffer;

  return emsg;
  }


static bool
request_complete(ccp msg, size_t size) {

  // Return true iff msg contains a complete http request message.  This is not
  // even close to being correct, but it works well enough for now.

  if (size < 4) 
    return false;

  if (not memcmp(msg + size - 2, "\n\n", 2))
    return true;
  if (not memcmp(msg + size - 3, "\n\n", 2))
    return true;

  for (int i = size - 4; i >= 0; i--) {
    if (not memcmp(msg + i, "\r\n\r\n", 4))
      return true;
    if (not memcmp(msg + i, "\n\n", 2))
      return true;
    }

  return false;
  }


static ccp
skip_space(ccp s, ccp e) {

  //  Advance s until it points to a non-space character or it's equal to e.

  while ((s < e) and (isspace(*s))) s++;

  return s;
  }


#ifdef TESTING

// g++ -gstabs -o test-main -DTESTING -ansi -pedantic main.cc ip-utils.cc -lsocket -lnsl && ./test-main

int
main() {

# define svr "www.monmouth.edu"
  std::string url = "http://" svr;
  spair host = extract_server_host(url);
  assert(host.first == svr);
  assert(host.second == "80");

  url = "http://" svr "/";
  host = extract_server_host(url);
  assert(host.first == svr);
  assert(host.second == "80");

# define prt "100"
  url = "http://" svr ":" prt;
  host = extract_server_host(url);
  assert(host.first == svr);
  assert(host.second == prt);

  url = "http://" svr ":" prt "/";
  host = extract_server_host(url);
  assert(host.first == svr);
  assert(host.second == prt);
  }

#else

int 
main(int argc, char * argv[]) {

  // Run the proxy.

  const std::string port = do_options(argc, argv);
  const int proxy = passive_tcp(port.c_str());

  while (true) {
    std::string uri;
    const int 
      client = accept_tcp(proxy),
      server = connect(client, uri);

    resource document;
    const std::string emsg = receive(server, document);
    if (not emsg.empty())
      std::cerr << "Error during server read:  " << emsg << ".\n";
    else {
      write(client, document.data, document.size);
      mitm(uri, document);
      }
    delete [] document.data;

    close(client);
    close(server);
    }

  close(proxy);
  }

#endif


// $Log: main.cc,v $
// Revision 1.3  2003/10/11 22:38:55  rclayton
// Naughty, naughty - delete document.data.
//
// Revision 1.2  2003/10/11 20:41:04  rclayton
// Parse the request to make sure everything's been read.
//
// Revision 1.1  2003/10/10 22:07:54  rclayton
// Initial revision
//
