/* *************************************************************
   RWhois Software

   Copyright (c) 1994 Scott Williamson and Mark Kosters
   Copyright (c) 1996-2000 Network Solutions, Inc.

   See the file LICENSE for conditions of use and distribution.
**************************************************************** */

/*
*  response.c:  file containing function to read from a socket.  to be
*  incorporated into rwhois library.
*/

/*
*  This function reads one line from the socket using fetch_data().
*  It then parses the string, looking for various rwhois response formats.
*  It stores schema in schema_name, and the attribute:value pair in
*  rw_avpair_struct.  If a blank line is encountered, it returns the data
*  structure rw_record_struct.
*/


/*
*   General Observations
*
*   In response to any directive, the server returns information in the
*   following format:
*
*   %<directive_name> <attribute>:<value>
*   In case the response contains more than one record, then the server
*   sends a %<directive> line between each record.
*
*   In response to any query, the server returns information in the
*   following format:
*
*   <schema>:<attribute>:<value>
*
*   with a blank line separating each record.
*
*   Referrals:  In cases wherein the response to a query is a
*   referral from the server, "referral" is stored in the schema_name, and
*   the referrals are broken into server, port, and soa.
*
*/

#include "rwhois.h"
#include "rwhois_internal.h"
#include "strutils.h"

static rw_attr_value_enum
parse_attribute_type(char *typecode)
{
  if (typecode[0] == 'S' ||
      typecode[0] == 's') {
    return SEE_ALSO;
  }

  if (typecode[0] == 'I' ||
      typecode[0] == 'i') {
    return ID;
  }

  return TEXT;
}

/* parse attribute:value */
static rw_avpair_struct *
parse_av_pair(char *av_pair_line)
{
  char             *p;
  char             *q;
  rw_avpair_struct *av_pair;

  av_pair = (rw_avpair_struct *) CALLOC(1, sizeof(*av_pair));

  p = strchr(av_pair_line, ':');
  /* no colons means this is malformed */
  if (! p) {
    FREE(av_pair);
    return NULL;
  }

  *p = '\0'; p++;
  av_pair->value = STRDUP(p);

  q = strchr(av_pair_line, ';');
  if (q) {
    *q = '\0'; q++;
    av_pair->attribute = STRDUP(av_pair_line);
    av_pair->attribute_header
      = (rw_header_struct *) CALLOC(1, sizeof(rw_header_struct));
    av_pair->attribute_header->type = parse_attribute_type(q);
    q--; *q = ';';
  } else {
    av_pair->attribute = STRDUP(av_pair_line);
  }

  p--; *p = ':';

  return av_pair;
}

static int
is_record_empty(rw_record_struct *rec)
{
  if (!rec) return TRUE;

  if (rec->referral) return FALSE;

  if (rec->text) return FALSE;

  if (!rec->av_pair_list) return TRUE;

  if (dl_list_empty(rec->av_pair_list)) return TRUE;

  return FALSE;
}

static rw_record_struct *
allocate_rw_record_struct(rw_record_struct *rec,
                          rw_server_struct *server)
{
  if (rec) return rec;

  rec = (rw_record_struct *) CALLOC(1, sizeof(*rec));
  rec->av_pair_list = dl_list_create(destroy_rw_avpair_struct);

  if (server) {
    rwhois_set_record_source_by_server(rec, server);
  }

  return rec;
}

static void
fill_out_record_info(rw_record_struct *rec, rw_avpair_struct *av)
{
  if (!rec) return;
  if (!av) return;
  if (NOT_STR_EXISTS(av->attribute)) return;
  if (NOT_STR_EXISTS(av->value)) return;

  if (STR_EQ(av->attribute, "Class-Name") ||
      STR_EQ(av->attribute, "CN")) {
    rec->schema_name = STRDUP(av->value);
  } else if (STR_EQ(av->attribute, "ID")) {
    rec->id = STRDUP(av->value);
  }
}

static char *
fetch_info_block(rw_server_struct   *server,
                 rw_response_struct *response)
{
  char   line[MAXBUFFER];
  char   *buffer     = NULL;
  size_t buffer_size = 0;
  size_t text_size   = 0;
  size_t line_length = 0;

  buffer      = (char *) MALLOC(MAXBUFFER);
  buffer_size = 0;

  buffer[0] = '\0';

  while ( rwhois_read(server, line, sizeof(line)) > 0 ) {
    if (STRN_EQ(line, "%info off", 9)) {
      break;
    }

    line_length = strlen(line);
    /* see if we need to grow the buffer */
    if ( (text_size + line_length + 2) > buffer_size &&
         buffer_size < MAXINFOTEXT) {
      buffer = (char *) REALLOC(buffer, buffer_size + MAXBUFFER);
      buffer_size += MAXBUFFER;
    }

    strcat(buffer, line);
    text_size += line_length;
    strcat(buffer, "\n");
    text_size++;
  }

  return buffer;
}

void
rwhois_set_record_source(rw_record_struct   *record,
                         char               *protocol,
                         char               *host,
                         int                port)
{
  rw_record_source_struct *source;

  assert(record);

  source = (rw_record_source_struct *) CALLOC(1, sizeof(*source));

  if (STR_EXISTS(protocol)) {
    source->protocol = STRDUP(protocol);
  }

  if (STR_EXISTS(host)) {
    source->host = STRDUP(host);
  }

  source->port = port;

  if (record->source) {
    destroy_rw_record_source_struct(record->source);
  }

  record->source = source;
}


void
rwhois_set_record_source_by_server(rw_record_struct   *record,
                                   rw_server_struct   *server)
{
  assert(server);

  rwhois_set_record_source(record, "rwhois", server->host, server->port);
}

rw_record_struct *
rwhois_fetch_record(rw_server_struct   *server,
                    rw_response_struct *response)
{
  int              fetch_ret;
  char             buffer[MAXBUFFER];
  char             error_buf[MAXBUFFER];
  fetch_state_enum state;
  rw_record_struct *record = NULL;

  assert(server);
  assert(response);

  if (! rwhois_is_open(server)) {
    rwhois_set_response(response, RW_ERROR,
                        "Connection to rwhois server not open");
    return NULL;
  }


  /* set initial state */
  state = RW_STATE_READ_LINE;

  /* aren't state machines much easier to figure out? ;-) */
  while (state != RW_STATE_FINISHED)
  {
    switch(state)
    {
    case RW_STATE_READ_LINE:
      fetch_ret = rwhois_read(server, buffer, sizeof(buffer));

      if (fetch_ret == 0) {
        rwhois_close(server);
        state = RW_STATE_FINISHED;
      } else if (fetch_ret < 0) {
        sprintf(error_buf, "Read from socket failed [%d]: %s", fetch_ret,
                strerror(errno));
        rwhois_set_response(response, RW_ERROR, error_buf);
        return NULL;
      }

      state = RW_STATE_DECODE_LINE;
      break;
    case RW_STATE_DECODE_LINE:
      state = decode_line(buffer, server->version);
      break;
    case RW_STATE_ERROR:
      rwhois_set_response(response, rwhois_extract_error_number(buffer),
                          rwhois_extract_error_msg(buffer));
      state = RW_STATE_FINISHED;
      break;
    case RW_STATE_OK:
      rwhois_set_response(response, RW_DONE, "");
      state = RW_STATE_FINISHED;
      break;
    case RW_STATE_INFO:
      /* load the info block into the current record */
      record = allocate_rw_record_struct(record, server);
      record->text = fetch_info_block(server, response);
      state = RW_STATE_READ_LINE;
      break;
    case RW_STATE_RWHOIS:
      server->version = get_version(buffer);
      get_capability(buffer, &(server->capability));
      rwhois_set_response(response, RW_OK, "");
      state = RW_STATE_READ_LINE;
      break;
    case RW_STATE_REFERRAL:
      record = allocate_rw_record_struct(record, server);

      record->referral = rwhois_parse_referral(buffer,
                                               server->version,
                                               NULL);

      if (!record->referral) {
        rwhois_set_response(response, RW_ERROR,
                            "Error while parsing referral");
      } else {
        rwhois_set_response(response, RW_FETCH_MORE, "");
      }
      state = RW_STATE_FINISHED;
      break;

    /* these are the record seperator states. They all lead to end states */
    case RW_STATE_DIRECTIVE_SEP:
    case RW_STATE_SCHEMA_SEP:
    case RW_STATE_DEFINE_SEP:
    case RW_STATE_DISPLAY_SEP:
    case RW_STATE_OBJECT_SEP:
    case RW_STATE_SOA_SEP:
    case RW_STATE_STATUS_SEP:
    case RW_STATE_XFER_SEP:
    case RW_STATE_CLASS_SEP:
    case RW_STATE_END_OF_RECORD:
      rwhois_set_response(response, RW_FETCH_MORE, "");
      state = RW_STATE_FINISHED;
      break;

    /* those things that follow the directive, field, value tuple */
    case RW_STATE_IN_DIRECTIVE:
    case RW_STATE_IN_SCHEMA:
    case RW_STATE_IN_DEFINE:
    case RW_STATE_IN_DISPLAY:
    case RW_STATE_IN_OBJECT:
    case RW_STATE_IN_SOA:
    case RW_STATE_IN_STATUS:
    case RW_STATE_IN_REGISTER:
    case RW_STATE_IN_CLASS:

    /* your probably saying to yourself "My god, why doesn't he make */
    /* functions for all of this. This switch is huge!" I'm doing it */
    /* so that I can see all of the special cases and states in one  */
    /* place. When its clean and working I'll break it up.           */

    /* In this state, the directive is the record's schema name, and
       the rest of the line is broken into av_pairs */
    {
      char *payload;
      rw_avpair_struct *cur_av;

      record = allocate_rw_record_struct(record, server);

      payload = strchr(buffer, ' ');
      if (payload) {
        *payload = '\0';

        if (!record->schema_name) {
          record->schema_name = STRDUP(buffer);
        }
        *payload = ' ';
        payload++;
      } else {
        payload = buffer;
      }

      cur_av = parse_av_pair(payload);

      if (cur_av) {
        dl_list_append(record->av_pair_list, cur_av);
        state = RW_STATE_READ_LINE;
      } else {
        rwhois_set_response(response, RW_ERROR,
                            "Improperly formatted directive record line");
        state = RW_STATE_FINISHED;
      }

      break;
    }
    /* the one thing that follows the directive, class, attr, value
       tuple, so the record schema_name is the class, not the
       directive name */
    case RW_STATE_IN_XFER:
    {
      char *schema;
      char *payload;
      rw_avpair_struct *cur_av;

      record = allocate_rw_record_struct(record, server);

      schema = strchr(buffer, ' ');
      if (!schema) {
        rwhois_set_response(response, RW_ERROR,
                            "Improperly formatted xfer line");
        state = RW_STATE_FINISHED;
        break;
      }

      schema++;

      payload = strchr(schema, ':');
      if (!payload) {
        rwhois_set_response(response, RW_ERROR,
                            "Improperly formatted xfer line");
        state = RW_STATE_FINISHED;
        break;
      }

      *payload = '\0';

      if (!record->schema_name) {
        record->schema_name = STRDUP(schema);
      }
      *payload = ':';
      payload++;

      cur_av = parse_av_pair(payload);
      if (cur_av) {
        dl_list_append(record->av_pair_list, cur_av);
        state = RW_STATE_READ_LINE;
      } else {
        rwhois_set_response(response, RW_ERROR,
                            "Improperly formatted xfer line");
        state = RW_STATE_FINISHED;
      }
      break;
    }
    /* the thing that follows the class, attribute, value tuple */
    case RW_STATE_IN_RECORD:
    {
      char             *payload;
      rw_avpair_struct *cur_av;

      record = allocate_rw_record_struct(record, server);

      if (count_char(buffer, ':') > 1) {
        payload = strchr(buffer, ':');
        if (!record->schema_name) {
          *payload = '\0';
          record->schema_name = STRDUP(buffer);
          *payload = ':';
        }
        payload++;
      } else {
        /* this isn't a normal case, but if we just start getting
           attr:value instead of schema:attr:value */
        payload = buffer;
      }

      cur_av = parse_av_pair(payload);

      if (cur_av) {
        fill_out_record_info(record, cur_av);
        dl_list_append(record->av_pair_list, cur_av);
        state = RW_STATE_READ_LINE;
      } else {
        rwhois_set_response(response, RW_ERROR,
                            "Improperly formatted record line");
        state = RW_STATE_FINISHED;
      }
      break;
    }
    case RW_STATE_FINISHED:
      break;
    default:
      rwhois_set_response(response, RW_ERROR,
                          "Unknown state encountered in rwhois_fetch_record");
      destroy_rw_record_struct(record);
      return NULL;
      break;
    }
  }

  return record;
}


dl_list_type *
rwhois_fetch_all_records(rw_server_struct   *server,
                         dl_list_type       *record_list,
                         rw_response_struct *response)
{
  rw_record_struct   *cur_record;
  int                not_done = TRUE;

  assert(server);
  assert(response);

  /* allocate the list */
  if (!record_list) {
    record_list = dl_list_create(destroy_rw_record_struct);
  }

  while (not_done) {
    cur_record = rwhois_fetch_record(server, response);

    if (response->status != RW_FETCH_MORE) {
      not_done = FALSE;
    }

    if (cur_record) {
      /* skip meaningless records (not a referral or a real record);
         we get these from the rwhois_fetch_record state machine,
         which allocates a record by default */
      if (is_record_empty(cur_record)) {
        destroy_rw_record_struct(cur_record);
        continue;
      }

      /* calculate the schema name if we haven't already */
      if (!cur_record->schema_name) {
        char *schema;

        if ( (schema = find_value_for_attribute(cur_record, "Class-Name")) ||
             ( schema = find_value_for_attribute(cur_record, "CN")) ) {
          cur_record->schema_name = STRDUP(schema);
        }
      }

      dl_list_append(record_list, cur_record);
    }
  }

  return record_list;
}


void
rwhois_print_record(rw_record_struct *record)
{

  if (!record || !record->av_pair_list ||
      dl_list_empty(record->av_pair_list)) {
    return;
  }

  if (record->schema_name[0] == '\0')
  {
    return;
  }
  printf("class: %s\n", record->schema_name);
  rwhois_print_avlist(record->av_pair_list);
}

void
rwhois_print_avlist(dl_list_type *av_pair_list)
{
  rw_avpair_struct *cur_av;
  int              not_done;

  if (!av_pair_list) return;

  not_done = dl_list_first(av_pair_list);

  while (not_done) {
    cur_av = (rw_avpair_struct *) dl_list_value(av_pair_list);

    if (!cur_av) {
      not_done = dl_list_next(av_pair_list);
      continue;
    }

    printf("%s: %s\n", SAFE_STR(cur_av->attribute, "none"),
           SAFE_STR(cur_av->value, "none"));
    not_done = dl_list_next(av_pair_list);
  }
}


void
rwhois_print_status(rw_server_struct        *server,
                    rw_server_status_struct *status)
{
  printf("STATUS:\n");

  printf("\tLimit = %d\n", status->limit);
  if (status->holdconnect == RW_ON)
    printf("\tHoldconnect = ON\n");
  else
    printf("\tHoldconnect = OFF\n");
  if (status->forward == RW_ON)
    printf("\tForward = ON\n");
  else
    printf("\tForward = OFF\n");
  if ( server->version == VER_1_5)
  {
    printf("\tObjects = %d\n", status->objects);
    printf("\tDisplay = %s\n", status->display);
    printf("\tContact = %s\n", status->contact);
  }
  else if (server->version == VER_1_0)
  {
    printf("\tLoad = %f\n", status->load);
    if (status->cache == RW_ON)
      printf("\tCache = ON\n");
    else
      printf("\tCache = OFF\n");

    printf("\tDisplay-Single = %s\n", status->display_single);
    printf("\tDisplay-Multi = %s\n", status->display_multi);
    printf("\tAuthority = %d\n", status->authority);
    printf("\tCached = %d\n", status->cached);
  }
}

int
destroy_rw_record_struct(rw_record_struct *record)
{
  if (!record) return TRUE;
  if (record->schema_name) FREE(record->schema_name);
  if (record->id) FREE(record->id);
  if (record->text) FREE(record->text);
  if (record->source) destroy_rw_record_source_struct(record->source);

  if (record->referral) destroy_rw_referral_struct(record->referral);
  if (record->av_pair_list) dl_list_destroy(record->av_pair_list);
  FREE(record);

  return TRUE;
}

int
destroy_rw_record_source_struct(rw_record_source_struct *source)
{
  if (!source) return TRUE;

  if (source->protocol) FREE(source->protocol);
  if (source->host)     FREE(source->host);

  FREE(source);

  return TRUE;
}

int
destroy_rw_avpair_struct(rw_avpair_struct *avpair)
{
  if (!avpair) return TRUE;

  if (avpair->attribute_header) {
    destroy_rw_header_struct(avpair->attribute_header);
  }

  if (avpair->attribute) FREE(avpair->attribute);
  if (avpair->value) FREE(avpair->value);

  FREE(avpair);

  return TRUE;
}

int
destroy_rw_header_struct(rw_header_struct *header)
{
  if (!header) return TRUE;

  if (header->language) FREE(header->language);
  if (header->charset) FREE(header->charset);
  if (header->encoding) FREE(header->encoding);
  if (header->other_headers) FREE(header->other_headers);

  FREE(header);

  return TRUE;
}
