
/* sock_post.c */
/* Andrew Davison, July 1997 (ad@ratree.psu.ac.th) */

/* Send a POST method and print the reply  */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <bstring.h>     /* for bzero(), bcopy() */
#include <unistd.h>      /* for read(), write(), close() */

#define SIZE 1024         /* max length of a string */
#define MAX_ENTRIES 100   /* max number of input fields */
#define MAXCHAR 2         /* size of hex chars array */


typedef struct {          /* for form nameString=valueString pairs */
    char *name;
    char *val;
} entry;



int extract_parts(char url[], char host[], char fnm[]);
void get_urlstr(char urlstr[]);
int input_entries(entry entries[]);
void encode_entries(entry entries[], int etnum);
char *encode_str(char str[]);
int should_encode(char ch);
void add_enc(char str[], int ch, int *i);
void itohc(int n, char s[]);
void show_entries(entry entries[], int etnum);
int open_socket(char *host, char *cmd);
void send_POST(int sd, char *appl, char *urlstr);
void show_reply(int sd);


int main()
{
  int sd;
  char url[SIZE], host[SIZE], appl[SIZE], urlstr[SIZE];

  printf("Enter a URL: ");
  fgets(url, SIZE, stdin);
  url[strlen(url)-1] = '\0';   /* overwrite '\n' */

  if (extract_parts(url, host, appl)) {
    get_urlstr(urlstr);
    sd = open_socket(host, "sock_post");
    send_POST(sd, appl, urlstr);
    show_reply(sd);
    close(sd);
  }
  return 0;
}



int extract_parts(char url[], char host[], char fnm[])
/*  url should be of the form:
        http://host[/fnm]
    host and fnm are extracted  from url; fnm may be empty
*/
{
  int i = 0, j, k;

  while ((url[i] != '\0') && (url[i] != '/'))
    i++;
  if (url[i] == '\0') {
    printf("Illegal URL format\n");
    return 0;
  }

  i = i + 2;    /* skip both '/'s */
  j = 0;
  while ((url[i] != '\0') && (url[i] != '/'))
    host[j++] = url[i++];      /* build host */
  host[j] = '\0';

  if (url[i] == '\0')          /* no file part */
    fnm[0] = '\0';
  else {
    i++;     /* skip the '/' */
    k = 0;
    while (url[i] != '\0')
      fnm[k++] = url[i++];     /* build fnm */
    fnm[k] = '\0';
  }
  return 1;
}


void get_urlstr(char urlstr[])
/* Read a series of name=value pairs into the entries array;
   URL encode the value parts, and then construct a single
   name1=value1&name2=valu2&... string in urlstr[].
*/
{
  int etnum, i;
  entry entries[MAX_ENTRIES];

  etnum = input_entries(entries);
  encode_entries(entries, etnum);
  show_entries(entries, etnum);

  urlstr[0] = '\0';
  for(i=0; i < etnum; i++) {
    strcat(urlstr, entries[i].name);
    strcat(urlstr, "=");
    strcat(urlstr, entries[i].val);
    strcat(urlstr, "&");
  }
  urlstr[strlen(urlstr)-1] = '\0';   /* overwrite last '&' */

  printf("Final URL string is:\n%s\n", urlstr);
}


int input_entries(entry entries[])
/* Fill in entries with name=value pairs from the keyboard. */
{
  int num = 0, namelen, linelen;
  char line[SIZE];

  printf("\nEnter name=value pairs:\n");

  while (gets(line) && (num < MAX_ENTRIES)) {
    linelen = strlen(line);
    namelen = strcspn(line, "=");
    entries[num].name =
      (char *)malloc(sizeof(char)*(namelen+1));
    strncpy(entries[num].name, line, namelen);
    entries[num].name[namelen] = '\0';
    entries[num].val =
      (char *)malloc(sizeof(char)*(linelen-namelen));
    strcpy(entries[num].val, &line[namelen+1]);
    num++;
  }
  return num;
}


void encode_entries(entry entries[], int etnum)
/* Encode the name/value strings so that (most) punctuation
   is represented by hex values, and spaces by '+'.
*/
{
  char *temp;
  int i;

  for(i=0; i < etnum; i++) {
    temp = entries[i].name;
    entries[i].name = encode_str(temp);
    free(temp);
    temp = entries[i].val;
    entries[i].val = encode_str(temp);
    free(temp);
  }
}


char *encode_str(char str[])
/* Check each character of str[], and either copy
   it across to newstr[] or copy its URL encoding.
   At the end return a malloced copy of the new string.
*/
{
  char newstr[SIZE], *result;
  int i = 0, j = 0;

  while (str[i] != '\0') {
    if (should_encode(str[i]))
      add_enc(newstr, str[i], &j);  /* replace by encoding */
    else if (str[i] == ' ')
      newstr[j++] = '+';            /* convert spaces to '+'s  */
    else
      newstr[j++] = str[i];         /* do nothing */
    i++;
  }
  newstr[j] = '\0';

  result = (char *)malloc((strlen(newstr)+1)*sizeof(char));
  strcpy(result, newstr);

  return result;
}


int should_encode(char ch)
/* Return 1 if the character should be changed, 0 otherwise.
   Basically, most punctuation should be encoded as well as
   non-printing ASCII. 
*/
{
  if ((ch == '_') || (ch == '-') ||
      (ch == '.') || (ch == '*') ||
      (ch == '@'))
    return 0;         /* do not change these symbols */

  if (ispunct(ch))
    return 1;         /* change other punctuation but not space */

  if ((ch <= 31) || (ch >= 127))
    return 1;         /* change ASCII non-printing chars */

  return 0;
}


void add_enc(char str[], int ch, int *i)
/* Change the ASCII integer in ch into its hexadecimal
   equivalent, and place that string in str[].
*/
{
  char hex[MAXCHAR];
  int j;

  str[(*i)++] = '%';              /* add % prefix */
  itohc(ch, hex);
  for(j=MAXCHAR-1; j >= 0; j--)   /* hex chars are in reverse order */
    str[(*i)++] = hex[j];
}


void itohc(int n, char s[])
/* Convert an ASCII value into hex characters in s */
/* It is stored in reverse. */
{
  int i = 0, digit;

  do {
    digit = n % 16;    /* get next digit */
    if (digit > 9)
      s[i++] = 'A' + digit - 10;
    else
      s[i++] = digit + '0';
  } while ((n /= 16) > 0);

  if (i == MAXCHAR-1)  /* only 1 hex char created */
    s[i] = '0';        /* add a '0' */
}



void show_entries(entry entries[], int etnum)
/* Print out the contents of the entries[] array */
{
  int i;
  printf("\nName=Value pairs:\n");
  for(i=0; i < etnum; i++)
    printf("%s=%s\n", entries[i].name, entries[i].val);
  printf("\n\n");
}



int open_socket(char *host, char *cmd)
/* Create a socket. Assign host's details to name.
   Create a stream using name and connect the socket to it.

   The lines marked with "CHG" are different from the 
   open_socket() function in "Programming with the HTTP Protocol"
   Web Techniques article.
*/
{
  struct sockaddr_in name;
  struct hostent *hptr;
  int sd;

  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {    /* CHG */
    perror(cmd);
    exit(1);
  }

  /* initialise the name struct to all zeros */        /* CHG */
  bzero(&name, sizeof(name)); 
/*  memset(&name, 0, sizeof(name)); */

  name.sin_family = AF_INET;
  name.sin_port = htons(80);         /* host is a HTTP server */ /* CHG */

  if ((hptr = gethostbyname(host)) == NULL) {
    perror("gethostbyname");
    exit(1);
  }

  /* copy the IP address of host into name */
  bcopy(hptr->h_addr, &name.sin_addr.s_addr, hptr->h_length); 
/*  memcpy(&name.sin_addr.s_addr, hptr->h_addr, hptr->h_length); */  /* CHG */
/*  name.sin_addr.s_addr = * (unsigned long *) hptr->h_addr; */

  printf("Trying to contact %s...\n", host);
  if (connect(sd, (struct sockaddr *) &name, sizeof(name)) < 0) {    /* CHG */
    close(sd);
    perror(cmd);
    exit(1);
  }

  return sd;
}


void send_POST(int sd, char *appl, char *urlstr)
/* Send the POST message, which consists of a POST line,
   a Content-type line, a Content-length line, a blank line,
   and finally the name1=value1&name2=value2&... pairs string.
*/
{
  char *ct = "Content-type: application/x-www-form-urlencoded\n";
  char line[SIZE];
  int ulen = strlen(urlstr);

  printf("Sending a POST request...\n");

  sprintf(line, "POST /%s HTTP/1.0\n", appl);
  write(sd, line, strlen(line));

  write(sd, ct, strlen(ct));

  sprintf(line, "Content-length: %d\n\n", ulen);
  write(sd, line, strlen(line));

  write(sd, urlstr, ulen);
}


void show_reply(int sd)
/* Print the reply to the POST message */
{
  char buf[SIZE];
  int no;

  printf("Waiting for a response\n");
  while ((no = read(sd, buf, SIZE)) > 0)
    write(1, buf, no);    /* write to stdout */
}



