/*
 *  $Id: talk.c,v 1.6 1994/05/25 07:46:30 bert Exp bert $
 *
 *  communication with client and parsing the request
 *  (part of diswww, a Discuss->WWW gateway)
 */

#ifndef lint
static char rcsid_talk[] = "$Id: talk.c,v 1.6 1994/05/25 07:46:30 bert Exp bert $";
#endif

#include "proto.h"  /* the copyright notice is included in this file, too. */
#include "extern.h"
#include "version.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <malloc.h>
#include "status.h"

/*** TODO: auto-confiscate this ***/
#if defined(__sun__) && !defined(__svr4__) && !defined(__NetBSD__)
#define SUNOS4
#endif

#if defined(SUNOS4)
#define memmove(a, b, c) bcopy(b, a, c)
#endif
/*** end of TODO ***/

/* status codes */
char S_ok[] =                "200 Document follows";
char S_moved[] =             "301 The meeting has been moved";
char S_bad_request[] =       "400 Bad request";
char S_forbidden[] =         "403 Forbidden";
char S_not_found[] =         "404 Not Found";
char S_internal_error[] =    "500 Internal error";
char S_not_implemented[] =   "501 Not implemented";

/* encoding types */
char T_plain[] = "text/plain";
char T_html[] =  "text/html";

/* error reply formats */
char E_not_found_fmt[] = "Document '%s' does not exist on this gateway.";
char E_bad_method_fmt[] = "Method '%s' isn't supported.";
char E_missing_method[] = "No method found in request.";
char E_mtg_error_fmt[] = "%s. (%s:%s)";
char E_trn_error_fmt[] = "%s. ([%d] in %s:%s)";

char error_title[] =
"<HEAD><TITLE>Error: %s</TITLE></HEAD>\r\n\
<H1>Error: %s</H1>\r\n";

char error_fmt[] = "<P>\r\n\
If you believe this is an error in the server and wish to report it,\r\n\
please send a mail message including the URL, information about your\r\n\
browser and any other relevant details to<P>\r\n\
<ADDRESS>%s</ADDRESS>\r\n";

char moved_message[] =
"<HEAD><TITLE>Document Moved</TITLE></HEAD>\r\n\
<H1>Document Moved</H1>\r\n\
The document you are looking for was moved to '%s'.<P>\r\n\
If you ever see this error message, then your client is way old.\r\n\
You should consider upgrading to a client that understands document\r\n\
forwarding.<P>\r\n\
<ADDRESS>%s</ADDRESS>\r\n";

/* random other static strings */
char header_fmt[] = 
"HTTP/1.0 %s\r\n\
Server: %s\r\n\
MIME-version: 1.0\r\n\
Content-type: %s\r\n";
char crlf[] = "\r\n";

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

/*
 *  read in a line of text.
 *  The line is supposed to be terminated by CRLF or LF.  
 *     rl --   structure containing the socket fd and buffering information
 *     line -- pointer to the line of text (passed by reference)
 *  returns: 0 on success, <0 on error
 *  Upon return, *line contains a line inside a fixed buffer (or NULL
 *  if stream is empty).  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;
#ifdef DEBUG
  } else {
    SYSLOG_NOTICE("broken client does not use CR.");
#endif
  }

  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;
}

/*
 *  notify the client on error.
 *     clt --    structure containing the socket fd
 *     status -- status code and description (e.g., "404 Not Found")
 *     fmt --    printf-style format to make the description of error
 *     ... --    printf-style arguments
 *  See also: send_moved_notice
 */
void send_error_notice(struct clt_sock *clt, char *status, char *fmt, ...)
{
  va_list pvar;

  if (clt->ver)
    send_header(clt, status, T_html);
  fd_print(clt->fd, error_title, status, status);

  va_start(pvar, fmt);
  vsprintf(w_buf, fmt, pvar);
  va_end(pvar);
  fd_write(clt->fd, w_buf);

  fd_print(clt->fd, error_fmt, SRV_WEBMASTER);

#ifdef DEBUG
  SYSLOG(LOG_DEBUG, "%s just got a %s", clt->host, status);
#endif
}

/*
 *  notify the client that a meeting was moved.
 *     clt --    structure containing the socket fd
 *     status -- status code and description (e.g., "303 The meeting moved")
 *     url --    URL to point the client at
 *  See also: send_error_notice
 */
void send_moved_notice(struct clt_sock *clt, char *status, char *url)
{
  if (clt->ver)
    send_header2(clt, status, T_html, 
		 "URI: ", url, "The meeting has moved. \r\n"
		 "Location: ", url, "\r\n", (char*)0);
  fd_print(clt->fd, moved_message, url, SRV_WEBMASTER);

#ifdef DEBUG
  SYSLOG(LOG_DEBUG, "%s just got pointed at %s", clt->host, url);
#endif
}

/*
 *  send the default HTTP/1.0 reply headers to the client.
 *     clt --    structure containing the socket fd
 *     status -- status code and description (e.g., "200 OK")
 *     type --   content type (must be "text/html" for errors)
 */
void send_header(struct clt_sock *clt, char *status, char *type)
{
  fd_print(clt->fd, header_fmt, status, DISWWW_VERSION, type);
  fd_write(clt->fd, crlf);
}

/*
 *  send default and specified HTTP/1.0 reply headers to the client.
 *     clt --    structure containing the socket fd
 *     status -- status code and description (e.g., "200 OK")
 *     type --   content type (must be "text/html" for errors)
 *     ... --    strings containing the additional headers
 */
void send_header2(struct clt_sock *clt, char *status, char *type, ...)
{
  va_list pvar;
  char *arg;

  fd_print(clt->fd, header_fmt, status, DISWWW_VERSION, type);
  va_start(pvar, type);
  while (arg = va_arg(pvar, char*))
    fd_write(clt->fd, arg);
  va_end(pvar);
  fd_write(clt->fd, crlf);
}

/*
 *  read the request and headers from the client.
 *     clt --    structure containing the socket fd
 *  returns: 0 on success, <0 on errors
 */
int talk_to_client(struct clt_sock *clt)
{
  char* line;

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

    /* read request */
    if (read_line(clt, &line) < 0) return -1;  /* error? */
    if (line == NULL) return -1;               /* 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, E_missing_method);
      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 = sstrdup(clt->method);
    clt->path =   sstrdup(clt->path);
    clt->ver =    sstrdup(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 = sstrdup(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, E_bad_method_fmt, clt->method);
    return -1;
  }

  return 0;
}
