/*

$Id: talk.c,v 1.1 1995/05/17 08:35:50 mwhitson Exp $

*/

#include "extern.h"
#include "proto.h"
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include "status.h"

char w_buf[SRV_REPLYSIZE];     /* temporary buffer for random output */

/*
 *  read_line reads a line of text from the socket in structure rl.
 *  The line is supposed to be terminated by CRLF or LF.  The function
 *  returns status (<0 on error).  A pointer to the line inside a
 *  buffer (or NULL if stream is empty) is returned in variable line.
 *  Note that this means any strings we want to keep must be copied
 *  away before the next read.
 */
int read_line(struct clt_sock *rl, char **line)
{
  int i, idx, size;

/*
 *  The part that has been read but not processed lies between
 *  rl->begin and rl->size-1, inclusively.
 *  (If rl->begin == rl->size, all data has been processed.)
 */

  /* check if we already have a LF */
  for (i = rl->begin; ((i < rl->size) && (rl->buf[i] != LF_CHAR)); i++);

  /* if we don't, move data to the beginning of the buffer */
  if ((i >= rl->size) && (rl->begin <= rl->size) && (rl->begin > 0)) {
    rl->size -= rl->begin;               /* after this, i stays >= rl->size */
    memmove(&rl->buf[0], &rl->buf[rl->begin], (size_t)rl->size);
    rl->begin = 0;
  }

  if (i >= rl->size) {    /* get a complete line */

    if (rl->size >= SRV_BUFSIZE) {     /* out of buffer space? */
      SYSLOG_WARNING("line too long, ask a wizard to shrink the client. =)");
      *line = NULL;
      return -1;       /* drop the connection */
    }

    /* assume select() already said reading was OK */

    idx = rl->size;

    /* read data and adjust rl->size */
    if ((size = read(rl->fd, &rl->buf[rl->size], SRV_BUFSIZE-rl->size)) <= 0) {
      *line = NULL;
      if (errno == EWOULDBLOCK)
	return 0;
      else {
	SYSLOG_EWARN("read from socket failed");
	return -2;       /* drop the connection */
      }
    }
    rl->size += size;

    /* check only the newly read portion for LF's */
    for (i = idx; ((i < rl->size) && (rl->buf[i] != LF_CHAR)); i++);
  }

  if (i >= rl->size) {
    *line = NULL;
    return 0;
  }

  /* strip [CR]LF and make the line into a \0-terminated string */
  rl->buf[i] = 0;
  if ((i > 0) && (rl->buf[i-1] == CR_CHAR)) {
    rl->buf[i-1] = 0;
  } else {
    SYSLOG_NOTICE("broken client does not use CR.");
  }

  idx = rl->begin;    /* keep the value of rl->begin for return... */
  rl->begin = i+1;    /* ...and point rl->begin to next line */
  *line = (&rl->buf[idx]);
  return 0;
}

/*
 *  This function is used to notify the client on error.  The first
 *  argument is the status code string.  The rest are the printf-style
 *  format and arguments to make the description of error condition.
 */
void send_error_notice(struct clt_sock *clt, char *status, char *fmt, ...)
{
  SYSLOG(LOG_DEBUG, "%s (%d) just got a %s", clt->host, clt->fd, status);
  /* it obviously doesn't do anything yet. */
}

/*
 *  This function is used to send the HTTP/1.0 reply header to the
 *  client.  The first argument is the status code string, and the
 *  second is the MIME content-type identifier.
 */
void send_header(struct clt_sock *clt, char *status, char *type)
{
  sprintf (w_buf, header_fmt, status, type);
  write(clt->fd, w_buf, strlen(w_buf));
}

/*
 *  perform a conversation with client (read request and output reply)
 */
int talk_to_client(struct clt_sock *clt)
{
  char* line;

  if (clt->reply) return 0;   /* already done? */

  if (! clt->method) {          /* is this the first line? */

    /* read request */
    if (read_line(clt, &line) < 0) return -1;  /* error? */
    if (line == NULL) return 0;                /* no data? */

    /* tokenize request into method, path and protocol version */
    if (! (clt->method = strtok(line, " "))) {      /* assume not HTTP/1.0 */
      send_error_notice(clt, S_bad_request, "No method found in request.");
      return -1;
    }
    if (! (clt->path = strtok(NULL, " "))) {
      clt->path = "/";
      clt->ver = NULL;
    } else {
      clt->ver = strtok(NULL," ");
    }

    /* replicate strings so buffer can change */
    clt->method = strdup(clt->method);
    clt->path =   strdup(clt->path);
    clt->ver =    strdup(clt->ver);
  }

  if (clt->ver) {            /* if not HTTP/1.0, we're done */
    do {                /* otherwise, read headers */
      if (read_line(clt, &line) < 0) return -1;  /* error? */
      if (line == NULL) return 0;                /* no data? */
      if (! strncasecmp("User-Agent: ",line,12))
	clt->agent = strdup(line+12);
      /* processing of headers goes here */
    } while ((*line) != (char)0);
  }

  /* if we're here, we have a complete request */

  if (strcasecmp(clt->method,"GET")) {
    send_error_notice(clt, S_not_implemented,
		      "Request '%s' isn't supported.", clt->method);
    return -1;
  }

  sprintf(w_buf, reply_fmt, clt->agent ? clt->agent : "nothing");
  clt->reply = strdup(w_buf);
  return 0;
}
