The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * copied and amended from a simple http socket program:
 * "Simple Internet client program - by Dan Drown <abob@linux.com>"
 * available (last I looked) at: 
 *      http://linux.omnipotent.net/article.php?article_id=5424
 */

#include "mhttp.h"


#ifdef DOHERROR
void herror(char *str){
  fprintf(stderr, "herror: %s\n", str);
}
#endif

bool mhttp_lets_debug;        /* global debugging flag           */
bool mhttp_body_set_flag;     /* global body set flag            */
bool mhttp_host_hdr;          /* I got a Host header             */
int mhttp_protocol = 0;
bool mhttp_first_init = false;

int  mhttp_hcnt,
     mhttp_rcode,
     mhttp_response_length;

char *mhttp_body,
     *mhttp_response,
     *mhttp_reason,
     mhttp_resp_headers[MAX_HDR_STR],
     *mhttp_headers[MAX_HEADERS],
     *mhttp_buffers[MAX_BUFFERS];

mhttp_conn_t mhttp_connection = NULL;

mhttp_conn_t mhttp_last_connection = NULL;

  

int mhttp_call(char *paction, char *purl)
{
    bool found_hdrs;        /* found the end of headers flag   */
    bool rcode_flag;        /* found return code flag          */
    bool newcon_flag;       /* new connection flag             */
    bool chunked;           /* transfer encoding is chunked    */
    char *action, *url, *host, *ptr, *eomsg, *clptr;
    char *req;
    char *surl;
    char *cert_str;
    int  port,
         returnval,
	 len,
	 curr_len,
	 buffer_size,
	 this_chunk,
	 i,
	 rem,
	 pos;
    char str[MAX_STR];


    newcon_flag = false;
    if (!mhttp_first_init)
        mhttp_init();
    memset(mhttp_resp_headers, 0, MAX_HDR_STR);

    mhttp_connection = mhttp_new_conn();

    if ((returnval = check_action(paction, &action)) < 0)
        return returnval;

    if ((returnval = check_url(purl, &url, &host)) < 0)
        return returnval;

    if ((port = get_port_and_uri(purl, host, &surl)) < 0)
        return port;
    mhttp_connection->host = strdup(host);
    mhttp_connection->port = port;

    mhttp_debug("action: %s host: %s port: %d url: %s", action, host, port, url);
    mhttp_debug("purl is: #%s#", purl);
    if (strncmp(purl, "https", 5) == 0)
           mhttp_connection->is_ssl = true;

    mhttp_debug("This connection: %s / %d / %d",
       mhttp_connection->host,
       mhttp_connection->port,
       mhttp_connection->is_ssl);

    if (mhttp_protocol == 1 && ! mhttp_host_hdr){
        mhttp_debug("This is HTTP/1.1 and we don't have a Host header");
        return -19;
    }

    if (mhttp_last_connection != NULL){
    mhttp_debug("OLD connection: %s / %d / %d",
       mhttp_last_connection->host,
       mhttp_last_connection->port,
       mhttp_last_connection->is_ssl);
     }
#ifdef GOTSSL
    if (mhttp_last_connection != NULL &&
       strcmp(mhttp_connection->host, mhttp_last_connection->host) == 0 &&
       mhttp_last_connection->port == mhttp_connection->port &&
       mhttp_last_connection->is_ssl == mhttp_connection->is_ssl){
       mhttp_connection->fd = mhttp_last_connection->fd;
       mhttp_connection->ctx = mhttp_last_connection->ctx;
       mhttp_connection->ssl = mhttp_last_connection->ssl;
       mhttp_connection->meth = mhttp_last_connection->meth;
       mhttp_connection->server_cert = mhttp_last_connection->server_cert;
#else
    if (mhttp_last_connection != NULL &&
       strcmp(mhttp_connection->host, mhttp_last_connection->host) == 0 &&
       mhttp_last_connection->port == mhttp_connection->port){
       mhttp_connection->fd = mhttp_last_connection->fd;
#endif
       mhttp_debug("using the cached connection");
    } else {
      if (mhttp_last_connection != NULL){
#ifdef GOTSSL
        if (mhttp_last_connection->is_ssl)
            SSL_shutdown (mhttp_last_connection->ssl);  /* send SSL/TLS close_notify */
#endif
        close(mhttp_last_connection->fd);
#ifdef GOTSSL
        if (mhttp_last_connection->is_ssl){
            mhttp_debug("shuting down the ssl engine");
            SSL_free (mhttp_last_connection->ssl);
            SSL_CTX_free (mhttp_last_connection->ctx);
        }
#endif
        mhttp_end_conn(mhttp_last_connection);
	mhttp_last_connection = NULL;
        mhttp_debug("Closed the last connection");
      }
      newcon_flag = true;
      mhttp_connection->fd = -1;
      mhttp_debug("didnt find connection - creating a new one: %s/%d - %d ", mhttp_connection->host, mhttp_connection->port, mhttp_connection->fd);
#ifdef GOTSSL
       if (mhttp_connection->is_ssl){
	   mhttp_debug("creating an SSL connection");
           //SSLeay_add_ssl_algorithms();
	   OpenSSL_add_ssl_algorithms();
           //mhttp_connection->meth = SSLv2_client_method();
	   mhttp_connection->meth = SSLv3_client_method();
           SSL_load_error_strings();
           mhttp_connection->ctx = SSL_CTX_new (mhttp_connection->meth);
	   if (mhttp_connection->ctx == NULL){
	       mhttp_debug("SSL_CTX_new failed - abort everything");
	       return -11;
	   }
          if( (mhttp_connection->fd = mhttp_connect_inet_addr(mhttp_connection->host, mhttp_connection->port)) < 0) {
               mhttp_debug("could not create a new socket: %d", mhttp_connection->fd);
               return mhttp_connection->fd;
           }
           // XXX set SSL here

           /* ----------------------------------------------- */
           /* Now we have TCP conncetion. Start SSL negotiation. */

           mhttp_debug("SSL craeting the ctx");
           mhttp_connection->ssl = SSL_new (mhttp_connection->ctx);
	   if (mhttp_connection->ssl == NULL){
	       mhttp_debug("SSL_new failed - abort everything");
	       return -12;
	   }
	   mhttp_debug("SSL set_verify");
	   SSL_CTX_set_default_verify_paths(mhttp_connection->ctx);
	   //SSL_CTX_load_verify_locations(mhttp_connection->ctx, "/etc/httpd/conf/ssl.crt/ca-bundle.crt",
	   //                                   NULL);
	   SSL_CTX_set_verify(mhttp_connection->ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, mhttp_verify_callback);
	   //SSL_CTX_set_verify(mhttp_connection->ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, NULL);
	   mhttp_debug("SSL set_fd");
           SSL_set_fd (mhttp_connection->ssl, mhttp_connection->fd);
           returnval = SSL_connect (mhttp_connection->ssl);
	   if (returnval == -1){
	       mhttp_debug("SSL_connect failed - abort everything");
	       ERR_print_errors_fp(stderr);
	       return -13;
	   }
    
           /* Get the cipher - opt */
           mhttp_debug ("SSL connection using %s\n", SSL_get_cipher (mhttp_connection->ssl));
  
           /* Get server's certificate (note: beware of dynamic allocation) - opt */
           mhttp_connection->server_cert = SSL_get_peer_certificate (mhttp_connection->ssl);
	   if (mhttp_connection->server_cert == NULL){
	       mhttp_debug("SSL_get_peer_certificate failed - abort everything");
	       return -14;
	   }
  
           cert_str = X509_NAME_oneline (X509_get_subject_name (mhttp_connection->server_cert),0,0);
	   if (cert_str == NULL){
	       mhttp_debug("X509_get_subject_name failed - abort everything");
	       return -15;
	   }
           mhttp_debug ("Certificate subject: %s\n", cert_str);
           free (cert_str);

           cert_str = X509_NAME_oneline (X509_get_issuer_name  (mhttp_connection->server_cert),0,0);
	   if (cert_str == NULL){
	       mhttp_debug("X509_get_issuer_name failed - abort everything");
	       return -16;
	   }
           mhttp_debug ("Certificate issuer: %s\n", cert_str);
           free (cert_str);

           /* We could do all sorts of certificate verification stuff here before
              deallocating the certificate. */

           if (SSL_get_verify_result(mhttp_connection->ssl) == X509_V_OK){
           /* The client sent a certificate which verified OK */
	       mhttp_debug("certificate OK");
           } else {
	       mhttp_debug("Certificate error: %s", X509_verify_cert_error_string(SSL_get_verify_result(mhttp_connection->ssl)));
	   }

           X509_free (mhttp_connection->server_cert);

      } else {
          if( (mhttp_connection->fd = mhttp_connect_inet_addr(mhttp_connection->host, mhttp_connection->port)) < 0) {
               mhttp_debug("could not create a new socket: %d", mhttp_connection->fd);
               return mhttp_connection->fd;
           }
      }
#else
      if( (mhttp_connection->fd = mhttp_connect_inet_addr(mhttp_connection->host, mhttp_connection->port)) < 0) {
           mhttp_debug("could not create a new socket: %d", mhttp_connection->fd);
           return mhttp_connection->fd;
       }
#endif
  }

  mhttp_debug("socket descriptor is: %d ", mhttp_connection->fd);

  len = 0;

  // construct the query string
  memset(str, 0, sizeof(str));

  if ((req = construct_request(action, surl)) == NULL)
  	return -2;

  returnval = write_socket(mhttp_connection, req, strlen(req));
  if(returnval < 0)
  {
    /* write returns -1 on error */
    perror("write(query string) error");
    return -5;
  }

  if(returnval < strlen(req))
  {
    /* I'm not dealing with this error, regular programs should. */
    perror("the query string write was short\n");
    return -6;
  }
  free(req);
  free(surl);
  free(url);
  free(host);

  // if this is a PUT or POST - write the body
  if (mhttp_body_set_flag)
  {
      // XXX we probably have a problem where 1024 is exceeded and need to rewrite
      mhttp_debug("this is a %s.... writing the body...", action);
      mhttp_debug("writing data: %s", mhttp_body);
      returnval = write_socket(mhttp_connection, mhttp_body, strlen(mhttp_body));
      if(returnval < strlen(mhttp_body))
      {
          /* I'm not dealing with this error, regular programs should. */
          fprintf(stderr, "the write of %s data was short\n", action);
          return -6;
      }
      returnval = write_socket(mhttp_connection, "\r\n", 2);
      if(returnval < 2)
      {
          /* I'm not dealing with this error, regular programs should. */
          fprintf(stderr, "the write of %s data - last line failed\n", action);
          return -7;
      }
  }
  free(action);


  /* read off the response and split out headers and content */
  mhttp_debug("starting output:");
  found_hdrs = false;
  rcode_flag = false;
  len = 0;
  curr_len = 0;
  chunked = false;

  if ((len = read_headers(mhttp_connection, str)) < 0){
    // we have no headers
    mhttp_debug("we have no headers ");
    exit(1);
  }
  if (mhttp_response_length > 0){
      buffer_size = mhttp_response_length;
  } else {
      buffer_size = MAX_STR;
  }


  mhttp_debug("initial len is: %d", len);
  while((returnval = read_socket(mhttp_connection, str)) > 0)
  {
       *(str+returnval) = '\0';
       // we have the headers - this is the body
       if (mhttp_response_length > 0){
	      // we dont know how big it should be so compensate
	      if (mhttp_connection->is_chunked){
	          if (len + returnval >= buffer_size - 2){
                     memcpy(mhttp_response+len, str, (buffer_size - 2) - len);
		     pos = (buffer_size - 2) - len;
		     len += pos;
		     mhttp_debug("len at end of chunk is: %d", len);

		     // find the next chunk
		     mhttp_debug("looking for the next chunk");
		     ptr = str+pos;
		     rem = returnval - pos;

	             if ((this_chunk = find_chunk(mhttp_connection, &ptr, &rem)) > 0){
			 // resize and paste on remainder
			 mhttp_debug("resize and paste on remainder for next chunk processing");
	                 mhttp_response = realloc(mhttp_response, buffer_size + this_chunk);
		         buffer_size += this_chunk;
                         memcpy(mhttp_response+len, ptr, rem);
			 returnval = rem;
	             } else if (this_chunk == 0){
	               // no more to come
		       mhttp_debug("we found the final chunk");
		       break;
	             }
	          } else {
		    // not end of chunk yet
                    memcpy(mhttp_response+len, str, returnval);
		  }
	     } else {
               // make sure that it does not overflow the buffer
               if (mhttp_response_length >= (len + returnval)){
                   memcpy(mhttp_response+len, str, returnval);
               }
           }
       } else {
           // we dont know how big it should be so compensate
           // else this is not a chunked read - so realloc when necessary
           if (len + returnval > buffer_size){
               mhttp_response = realloc(mhttp_response, buffer_size + MAX_STR);
               buffer_size += MAX_STR;
           }
           memcpy(mhttp_response+len, str, returnval);
       }
       len += returnval;


      // lets get out of here if we have read enough
      if (mhttp_response_length > 0 && len >= mhttp_response_length )
          break;
  }
  mhttp_debug("content length actually copied: %d (may include \\r\\n)", len);
  mhttp_response_length = len;

  /* it will be closed anyway when we exit */
  if (mhttp_protocol == 0 ||
      (clptr = strstr(mhttp_resp_headers, "Connection: close")) ||
      (clptr = strstr(mhttp_resp_headers, "Connection: Close")) ){
#ifdef GOTSSL
      if (mhttp_connection->is_ssl)
          SSL_shutdown (mhttp_connection->ssl);  /* send SSL/TLS close_notify */
#endif
      /* Clean up. */
      close(mhttp_connection->fd);
#ifdef GOTSSL
      if (mhttp_connection->is_ssl){
          mhttp_debug("shuting down the ssl engine");
          SSL_free (mhttp_connection->ssl);
          SSL_CTX_free (mhttp_connection->ctx);
      }
#endif
      if (mhttp_last_connection != NULL){
          mhttp_end_conn(mhttp_last_connection);
	  mhttp_last_connection = NULL;
          mhttp_debug("removed socket name");
      }
      mhttp_debug("Closed the connection");
  } else {
      if (newcon_flag){
          if (mhttp_last_connection != NULL){
              mhttp_end_conn(mhttp_last_connection);
	      mhttp_last_connection = NULL;
	  }
	  mhttp_last_connection = mhttp_new_conn();
          mhttp_last_connection->host = strdup(mhttp_connection->host);
          mhttp_last_connection->port = mhttp_connection->port;
          mhttp_last_connection->fd = mhttp_connection->fd;
#ifdef GOTSSL
          if (mhttp_connection->is_ssl){
	      mhttp_debug("saving SSL stuff");
              mhttp_last_connection->is_ssl = mhttp_connection->is_ssl;
              mhttp_last_connection->ssl = mhttp_connection->ssl;
              mhttp_last_connection->ctx = mhttp_connection->ctx;
	  }
#endif
          mhttp_debug("Caching the connection");
      } else {
          mhttp_debug("connection allready cached");
      }
  }
  mhttp_end_conn(mhttp_connection);
  mhttp_connection = NULL;
  mhttp_debug("all done");
  return 1;
}


int find_content_length(void){
    char *ptr;
    int rem = 0;

    // determine the Content-Length header
    if ((ptr = strstr(mhttp_resp_headers, "Content-Length:")) ||
        (ptr = strstr(mhttp_resp_headers, "Content-length:"))){
        mhttp_debug("found content-length");
        ptr += 16;
        mhttp_response_length = atoi(ptr);
        mhttp_debug("content length: %d", mhttp_response_length);
        mhttp_response = malloc(mhttp_response_length + 2);
	return mhttp_response_length;
    }
    return 0;

}


bool find_transfer_encoding(void){
    char *clptr;
    int this_chunk;

    /* look for Transfer-Encoding: chunked */
    // clptr the chunk pointer
    // ptr the beginning of the body
    if ((clptr = strstr(mhttp_resp_headers, "Transfer-Encoding:")) ||
        (clptr = strstr(mhttp_resp_headers, "Transfer-encoding:"))){
        clptr += 19;
        if (strncmp(clptr, "chunked",7) == 0){
            mhttp_debug("found Transfer-Encoding: chunked");
	    return true;
	 }
    }
    return false;

}


int find_chunk(mhttp_conn_t conn, char **ptr, int *rem){
    char *clptr;
    char *myptr;
    int myrem;
    int diff;
    int this_chunk;
    int returnval;

    /* look for Transfer-Encoding: chunked */
    // clptr the chunk pointer
    // ptr the beginning of the body

    myptr = *ptr;
    myrem = *rem;

    // make sure that there is enuf of ptr left
    mhttp_debug("remainder is: %d", myrem);
    if (myrem <= 2 || !(clptr = strstr(myptr, "\r\n"))){
        mhttp_debug("getting another line");
        if ((returnval = read_socket(conn, myptr+myrem)) > 0){
            myrem += returnval;
            *(myptr+myrem) = '\0';
	    mhttp_debug("got another line: %d - #%s#", returnval, myptr);
	} else {
            mhttp_debug("cant get another line - aborting");
	    return -17;
	}
    }

    // must find the end of the chunked length line 
    // in the remainding buffer
    if (clptr = strstr(myptr, "\r\n")){
        mhttp_debug("looking for chunk in: %s#", myptr);
        if (sscanf(myptr, "%x\r\n", &this_chunk) != 1){
             mhttp_debug("count not the chunked amount - something ify");
            if ((returnval = read_socket(conn, myptr+myrem)) > 0){
                myrem += returnval;
                *(myptr+myrem) = '\0';
	        mhttp_debug("got another line: %d - #%s#", returnval, myptr);
		if (strncmp(myptr, "\r\n", 2) == 0){
		   myptr+=2;
		   myrem-=2;
		}
                mhttp_debug("looking for chunk in: #%s#", myptr);
                if (sscanf(myptr, "%x\r\n", &this_chunk) != 1){
                     mhttp_debug("count not the chunked amount - something broken");
	             return -17;
                }
	     }
             return -17;
	 }
	 // shift past the chunk size
	 *clptr = '\0';
	 myrem -= strlen(myptr) + 2;
	 clptr += 2;
         mhttp_debug("Transfer-Encoding: chunked buffer is %d - %d bytes left: %s", this_chunk, myrem, clptr+myrem);

	 *ptr = clptr;
	 *rem = myrem;
         return this_chunk;
    }
    return -17;

}



int read_headers(mhttp_conn_t conn, char *str){
  int returnval;
  int curr_len;
  int rem;
  int this_chunk;
  char *ptr, *eomsg;
  bool rcode_flag;


  rcode_flag = false;
  curr_len = 0;
  while((returnval = read_socket(conn, str)) > 0)
  {
       *(str+returnval) = '\0';
       mhttp_debug("Header line %d: %s", returnval, str);

       if (strlen(mhttp_resp_headers) + returnval > MAX_HDR_STR){
           mhttp_debug("have not found the headers within MAX_HDR_STR: %d", MAX_HDR_STR);
           return -18;
       }

       /* detect the end of the headers */
       sprintf(mhttp_resp_headers+strlen(mhttp_resp_headers), "%s", str);

       /* find the return code        */
       if (!rcode_flag &&
           strncmp(str, "HTTP/",5) == 0 && 
           (strncmp(str+5, "0.9 ",4) == 0 ||
 	    strncmp(str+5, "1.0 ",4) == 0 ||
 	    strncmp(str+5, "1.1 ",4) == 0 ) ){
           ptr = str+9;
	   *(ptr+3) = '\0';
           mhttp_rcode = atoi(ptr);
	   rcode_flag = true;
	   ptr+=4;
	   /* find the status reason */
           if ((eomsg = strstr(ptr, "\r\n")) || (eomsg = strstr(ptr, "\n"))){
	       *eomsg = '\0';
	       mhttp_reason = strdup(ptr);
	   }
           mhttp_debug("detected return code: %d - %s", mhttp_rcode, mhttp_reason);
       }

       if ((ptr = strstr(mhttp_resp_headers, "\r\n\r\n")) ||
           (ptr = strstr(mhttp_resp_headers, "\n\n"))){
          *ptr = '\0';
          mhttp_debug("found end of headers at: %d", strlen(mhttp_resp_headers));
          mhttp_debug("headers are: %s", mhttp_resp_headers);
          if (strncmp(ptr,"\0\n\r\n",4) == 0){
              /* how far along the current buffer is the eoh marker */
              curr_len = (strlen(mhttp_resp_headers) + 4) - curr_len;
              ptr+=4;
          } else {
              curr_len = (strlen(mhttp_resp_headers) + 2) - curr_len;
              ptr+=2;
          }

          /* tidy up the first bit of the body XXX */
	  mhttp_debug("returnval: %d - curr_len: %d", returnval, curr_len);
	  rem = returnval - curr_len;
	  mhttp_debug("the remainder is: %d", rem);

          // find the Content-Length header
          if ( find_content_length() > 0 ){
              if (mhttp_response_length >= rem){
                  mhttp_debug("copying the initial part of the body: %s", ptr);
                  memcpy(mhttp_response, ptr, rem);
                  return rem;
              } else {
                  // serious error - cant determine length properly
                  mhttp_debug("serious error - cant determine length properly");
                  return -8;
              }
           // or find the Tranfer-Enconding: chunked header
           } else if (find_transfer_encoding()){
	       // find the chunk value
	       conn->is_chunked = true;
	       if ((this_chunk = find_chunk(conn, &ptr, &rem)) > 0){
                   mhttp_response = malloc(this_chunk + 2);
                   memcpy(mhttp_response, ptr, rem);
                   mhttp_response_length = this_chunk + 2;
		   return rem;
	       } else if (this_chunk == 0){
	         // an empty body 
		 return 0;
	       } else {
	          // failed to find the next chunk value
		  mhttp_debug("cannot find \\r\\n after first chunked marker - time to give up");
		  return -17;
	       }
           } else {
	      mhttp_debug("didnt find content-length - must use realloc: %d", rem);
              mhttp_response_length = 0;
              mhttp_response = malloc(MAX_STR);
              memcpy(mhttp_response, ptr, rem);
	      return rem;
	   }
           // or determine that it is HTTP/1.0
           // or find the Connection: close
          return curr_len;
      }
      curr_len += returnval;
  }

  /* must have hit an error */
  return returnval;

}


int check_url(char *purl, char **url, char **host){

    char *ptrhost;

    if(strlen(purl) == 0)
    {
        mhttp_debug("must supply a url");
        return -3;
    }

    if (strncmp(purl, "http://", 7) == 0){
        ptrhost = purl+7;
#ifdef GOTSSL
    } else if (strncmp(purl, "https://", 8) == 0){
        ptrhost = purl+8;
	mhttp_debug("setting the ssl flag");
	mhttp_connection->is_ssl = true;
#endif
    } else {
        mhttp_debug("url must start with http:// - and yep we dont support https\n");
        return -4;
    }
    *url = strdup(purl);
    *host = strdup(ptrhost);
    mhttp_debug("begin of host is: %s", *host);
    return 0;
}



int get_port_and_uri(char *url, char *host, char **surl){
  char *ptr;
  int port;

  // hunt for the beginning of the uri
  mhttp_debug("begin looking for host at: %s", host);
  *surl = malloc(MAX_STR);
  ptr = strstr(host, "/");
  if (ptr != NULL){
      *ptr = '\0';
      ptr++;
      sprintf(*surl, "/%s", ptr);
  } else {
      sprintf(*surl, "/");
  }
  // hunt for the beginning of the port
  ptr = strstr(host, ":");
  if (ptr != NULL){
      *ptr = '\0';
      ptr++;
      port = atoi(ptr);
  } else {
#ifdef GOTSSL
      if (strncmp(url ,"https", 5) == 0){
          port = 443;
      } else {
          port = 80;
      }
#else
      port = 80;
#endif

  }
  return port;

}
 

#ifdef GOTSSL
static int mhttp_verify_callback(int ok, X509_STORE_CTX* ctx)
{
    return 1;
}
#endif

mhttp_conn_t mhttp_new_conn(void){
    mhttp_conn_t new_conn;

    new_conn = (mhttp_conn_t) malloc(sizeof(struct mhttp_conn_st));
    memset(new_conn, 0, sizeof(struct mhttp_conn_st));

    new_conn->host = NULL;
    new_conn->port = 0;
    new_conn->is_ssl = false;
    new_conn->is_chunked = false;
    return new_conn;
}


void mhttp_end_conn(mhttp_conn_t conn){

    mhttp_debug("resetting conn");
    free(conn->host);
    free(conn);

}


char *construct_request(char *action, char *url){
  char *str;
  int i;

  str = malloc(MAX_HDR_STR);

  strcpy(str, action);
  strcpy(str+strlen(str), " ");
  strcpy(str+strlen(str), url);
  sprintf(str+strlen(str), " HTTP/1.%d\r\n", mhttp_protocol);
  mhttp_debug("adding on the headers: %d", mhttp_hcnt);
  for(i = 0; i < mhttp_hcnt; i++)
  {
      // make sure that we dont exceed the buffer
      if ((strlen(str) + strlen(mhttp_headers[i])) > MAX_BUFFERS - 1)
          break;
      mhttp_debug("adding header: %s", mhttp_headers[i]);
      sprintf(str+strlen(str), "%s\r\n", mhttp_headers[i]);
  }
  // if this is a post - add the Content-Length header
  if (mhttp_body_set_flag)
  {
      sprintf(str+strlen(str), 
              "Content-Length: %d\r\n\r\n", strlen(mhttp_body));
  } else {
      strcpy(str+strlen(str), "\r\n\r\n");
  }
  mhttp_debug("query string + headers are: %s", str);

  return str;

}


int check_action(char *paction, char **action){
    if(strlen(paction) == 0)
    {
        mhttp_debug("must supply an action");
        return -2;
    }

    if (strcmp(paction, "GET") != 0 && 
        strcmp(paction, "POST") != 0 && 
        strcmp(paction, "PUT") != 0 &&
        strcmp(paction, "DELETE") != 0 &&
        strcmp(paction, "HEAD") != 0)
    {
        mhttp_debug("must supply an action of GET, PUT, POST, DELETE, or HEAD");
        return -1;
    }
    *action = strdup(paction);
    mhttp_debug("The action is: %s", *action);
    return 0;
}


void mhttp_switch_debug(int set)
{
    if (!mhttp_first_init)
        mhttp_init();
     
    if (set > 0)
    {
        mhttp_lets_debug = true;
#ifdef GOTSSL
        mhttp_debug("%s", "switched on debugging(SSL Support running)...");
#else
        mhttp_debug("%s", "switched on debugging...");
#endif
    } else {
        mhttp_lets_debug = false;
    }

}


void mhttp_set_protocol(int proto)
{

    if (!mhttp_first_init)
        mhttp_init();
     mhttp_protocol = proto;

}


int mhttp_get_status_code(void)
{

    return mhttp_rcode;

}


int mhttp_get_response_length(void)
{

    return mhttp_response_length;

}


char *mhttp_get_reason(void)
{

  if (mhttp_reason != NULL){
    mhttp_debug("the reason is: %s", mhttp_reason);
    return strdup(mhttp_reason);
  } else {
    return NULL;
  }

}


char *mhttp_get_response(void)
{

    return mhttp_response;

}


char *mhttp_get_response_headers(void)
{

    return strdup(mhttp_resp_headers);

}


void mhttp_reset(void)
{

  int i;

    if (!mhttp_first_init)
        mhttp_init();
    if (mhttp_response != NULL){
        free(mhttp_response);
        mhttp_response = NULL;
        mhttp_debug("reset the response");
    }
    mhttp_response_length = 0;
    if (mhttp_reason != NULL){
        free(mhttp_reason);
        mhttp_reason = NULL;
        mhttp_debug("reset the reason");
    }
    if (mhttp_body_set_flag)
        free(mhttp_body);
    mhttp_body_set_flag = false;
    mhttp_rcode = 0;
  
    mhttp_debug("finished reset");

}


void mhttp_init(void)
{

  int i;

  mhttp_first_init = true;
  for (i = 0; i < mhttp_hcnt; i++)
  {
      free(mhttp_headers[i]);
      mhttp_debug("freeing header");
      mhttp_headers[i] = NULL;
  }
  mhttp_hcnt = 0;
  mhttp_lets_debug = false;
  mhttp_protocol = 0;
  mhttp_host_hdr = false;
  mhttp_reset();
  mhttp_debug("finished init");
}


void mhttp_add_header(char *hdr)
{

    if (!mhttp_first_init)
        mhttp_init();

    /* Do we have a Host Header?                          */
    if (! mhttp_host_hdr && strncmp("Host:", hdr, 5) == 0)
        mhttp_host_hdr = true;

    mhttp_headers[mhttp_hcnt++] = strdup(hdr);
    mhttp_debug("request header %s", mhttp_headers[mhttp_hcnt - 1]);
    mhttp_headers[mhttp_hcnt] = NULL;

}


void mhttp_set_body(char *bdy)
{

    // assumes body is a string XXX
    if (!mhttp_first_init)
        mhttp_init();
    mhttp_body = strdup(bdy);
    mhttp_debug("setting body: %s", mhttp_body);
    mhttp_body_set_flag = true;

}


int read_socket(mhttp_conn_t conn, void *buf){
  int returnval;

#ifdef GOTSSL
  if (conn->is_ssl){
      returnval = SSL_read (conn->ssl, buf, READ_BUF);
      if (returnval == -1){
          mhttp_debug("SSL_read failed - abort everything");
          ERR_print_errors_fp(stderr);
          return -16;
      }
  } else {
      returnval = read(conn->fd, buf, READ_BUF);
  }
#else
  returnval = read(conn->fd, buf, READ_BUF);
#endif
  return returnval;

}



int write_socket(mhttp_conn_t conn, const void *buf, size_t count){
  int returnval;

#ifdef GOTSSL
  if (conn->is_ssl){
      //mhttp_debug("writing to ssl connection");
      returnval = SSL_write (conn->ssl, buf, count);
      if (returnval == -1){
          mhttp_debug("SSL_write failed - abort everything");
          ERR_print_errors_fp(stderr);
          return -17;
      }
  } else {
      returnval = write(conn->fd, buf, count);
  }
#else
  returnval = write(conn->fd, buf, count);
#endif
  return returnval;

}


int mhttp_connect_inet_addr(const char *hostname, unsigned short int port)
{
  int inet_socket; /* socket descriptor */
  struct sockaddr_in inet_address; /* IP/port of the remote host to connect to */

  if ( mhttp_build_inet_addr(&inet_address, hostname, port) < 0 )
      return -1;

  /* socket(domain, type, protocol) */
  inet_socket = socket(PF_INET, SOCK_STREAM, 0);

  mhttp_debug("socket no: %d", inet_socket);
  /* domain is PF_INET(internet/IPv4 domain) *
   * type is SOCK_STREAM(tcp) *
   * protocol is 0(only one SOCK_STREAM type in the PF_INET domain
   */

  if (inet_socket < 0)
  {
    /* socket returns -1 on error */
    perror("socket(PF_INET, SOCK_STREAM, 0) error");
    mhttp_debug("socket(PF_INET, SOCK_STREAM, 0) error");
    return -2;
  }

  /* connect(sockfd, serv_addr, addrlen) */
  if(connect(inet_socket, (struct sockaddr *)&inet_address, sizeof(struct sockaddr_in)) < 0)
  {
    /* connect returns -1 on error */
    perror("connect(...) error");
    mhttp_debug("connect(...) error");
    return -3;
  }

  return inet_socket;
}


int mhttp_build_inet_addr(struct sockaddr_in *addr, const char *hostname, unsigned short int port)
{
  struct hostent *host_entry;

  /* gethostbyname(name) */
  host_entry = gethostbyname(hostname);

  if(host_entry == NULL)
  {
    /* gethostbyname returns NULL on error */
    herror("gethostbyname failed");
    mhttp_debug("gethostbyname failed");
    return -1;
  }

  /* memcpy(dest, src, length) */
  memcpy(&addr->sin_addr.s_addr, host_entry->h_addr_list[0], host_entry->h_length);
  /* copy the address to the sockaddr_in struct. */

  /* set the family type (PF_INET) */
  addr->sin_family = host_entry->h_addrtype;

  /* addr->sin_port = port won't work because they are different byte
   * orders
   */
  addr->sin_port = htons(port);

  /* just to be pedantic... */
  return 1;
}

/* debug logging */
void mhttp_debug(const char *msgfmt, ...)
{
    va_list ap;
    char *pos, message[MAX_STR];
    int sz;
    time_t t;

    if (!mhttp_lets_debug)
        return;

    /* timestamp */
    t = time(NULL);
    pos = ctime(&t);
    sz = strlen(pos);
    /* chop off the \n */
    pos[sz-1]='\0';

    /* insert the header */

    snprintf(message, MAX_STR, "mhttp debug:%s: ", pos);
        
    /* find the end and attach the rest of the msg */
    for (pos = message; *pos != '\0'; pos++); //empty statement
    sz = pos - message;
    va_start(ap, msgfmt);
    vsnprintf(pos, MAX_STR - sz, msgfmt, ap);
    fprintf(stderr,"%s", message);
    fprintf(stderr, "\n");
    fflush(stderr);
}