/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: httplib.c 2514 2011-09-23 18:30:55Z brachman $";
#endif

#ifdef DSSLIB
#include "dsslib.h"
#else
#include "local.h"
#endif

static char *log_module_name = "httplib";

char *
http_method_to_string(Http_method method)
{

  if (method == HTTP_GET_METHOD)
	return("GET");
  if (method == HTTP_POST_METHOD)
	return("POST");
  if (method == HTTP_HEAD_METHOD)
	return("HEAD");
  if (method == HTTP_PUT_METHOD)
	return("PUT");
  if (method == HTTP_DELETE_METHOD)
	return("DELETE");
  if (method == HTTP_OPTIONS_METHOD)
	return("OPTIONS");
  if (method == HTTP_TRACE_METHOD)
	return("TRACE");
  if (method == HTTP_CONNECT_METHOD)
	return("CONNECT");

  return(NULL);
}

Http_method
http_string_to_method(char *m)
{
  Http_method method;

  if (strcaseeq(m, "get"))
	method = HTTP_GET_METHOD;
  else if (strcaseeq(m, "post"))
	method = HTTP_POST_METHOD;
  else if (strcaseeq(m, "head"))
	method = HTTP_HEAD_METHOD;
  else if (strcaseeq(m, "put"))
	method = HTTP_PUT_METHOD;
  else if (strcaseeq(m, "delete"))
	method = HTTP_DELETE_METHOD;
  else if (strcaseeq(m, "options"))
	method = HTTP_OPTIONS_METHOD;
  else if (strcaseeq(m, "trace"))
	method = HTTP_TRACE_METHOD;
  else if (strcaseeq(m, "connect"))
	method = HTTP_CONNECT_METHOD;
  else
	method = HTTP_UNKNOWN_METHOD;

  return(method);
}

/*
 * RFCs 2145, 2616 (3.1)
 */
Http_version *
http_parse_version(char *version_str)
{
  unsigned int major, minor;
  char *endp;
  Http_version *version;

  if (strnumx(version_str, STRNUM_UI, &major, &endp) == -1)
	return(NULL);
  if (*endp != '.')
	return(NULL);
  if (strnumx(endp + 1, STRNUM_UI, &minor, &endp) == -1)
	return(NULL);
  if (*endp != '\0')
	return(NULL);

  version = ALLOC(Http_version);
  version->major = major;
  version->minor = minor;

  return(version);
}

static Uri_query_arg *
parse_query_arg(char *str)
{
  char *p, *q, *s;
  Uri_query_arg *arg;

  p = strdup(str);

  if ((q = strchr(p, '=')) != NULL)
	*q++ = '\0';

  if ((s = url_decode(p, NULL, NULL)) == NULL)
	return(NULL);

  arg = ALLOC(Uri_query_arg);
  arg->name = s;
  arg->value = "";

  if (q != NULL && *q != '\0')  {
	if ((s = url_decode(q, NULL, NULL)) == NULL)
	  return(NULL);
	arg->value = s;
  }

  return(arg);
}

/*
 * The QUERY_STRING is modified in-place.
 */
Dsvec *
parse_query_string(char *query_string)
{
  char *q, *s;
  Dsvec *dsv;
  Uri_query_arg *elem;

  q = query_string;
  dsv = dsvec_init(NULL, sizeof(Uri_query_arg *));

  while (1) {
	if ((s = strchr(q, '&')) != NULL)
	  *s++ = '\0';
	if ((elem = parse_query_arg(q)) == NULL)
	  return(NULL);
	dsvec_add_ptr(dsv, elem);

	if (s == NULL)
	  break;
	q = s;
  }

  return(dsv);
}

Kwv *
query_args_to_kwv(Dsvec *qa)
{
  int i;
  Kwv *kwv;
  Uri_query_arg *elem;

  if (dsvec_len(qa) == 0) {
	kwv = kwv_init(5);
	return(kwv);
  }

  kwv = kwv_init(dsvec_len(qa));
  for (i = 0; i < dsvec_len(qa); i++) {
	elem = (Uri_query_arg *) dsvec_ptr_index(qa, i);
	
	kwv_add(kwv, elem->name, elem->value);
  }

  return(kwv);
}

char *
kwv_to_query_string(Kwv *kwv)
{
  int i;
  char *n, *v;
  Ds ds;
  Kwv_iter *iter;
  Kwv_pair *pair;

  if (kwv == NULL)
	return(NULL);

  ds_init(&ds);
  iter = kwv_iter_begin(kwv, NULL);
  i = 0;
  while ((pair = kwv_iter_next(iter)) != NULL) {
	n = percent_encode_chars(pair->name, "%;?&=", 0);
	if (pair->val != NULL)
	  v = percent_encode_chars(pair->val, "%;?&=", 0);
	else
	  v = NULL;

	if (v == NULL)
	  ds_asprintf(&ds, "%s%s", (i != 0) ? "&" : "", n);
	else
	  ds_asprintf(&ds, "%s%s=%s", (i != 0) ? "&" : "", n, v);
	i++;
  }
  kwv_iter_end(iter);

  return(ds_buf(&ds));
}

/*
 * URL is a URL-style %XX encoded string.
 * RFC 2396 URI Generic Syntax
 * In the normal case, decoding is successful and the string contains no
 * "troublesome" characters; that string is returned, NON_PRINTABLE (if
 * non-NULL) is set to zero, and URL_DSP (if non-NULL) is set to the
 * dynamic string.
 *
 * Special cases can occur, however.  The decoded string may contain
 * problem characters or URL may not be properly encoded.
 * At present, only null characters are classified as a problem character.
 *
 * If decoding fails, NULL is returned, NON_PRINTABLE (if present) is set
 * to -1, and URL_DSP (if present) is set to NULL.
 *
 * If decoding is successful but contains at least one problem
 * character, NULL is returned, NON_PRINTABLE (if present) is set to the
 * number of problem characters, and URL_DSP (if non-NULL) is set to
 * the dynamic string (which may contain arbitrary bytes).
 *
 * If URL_DSP is set to a dynamic string, that string will always be
 * null-terminated.
 */
char *
url_decode(char *url, int *non_printable, Ds **url_dsp)
{
  int ch, nonp;
  char *p;
  Ds d, *ds;

  if (url_dsp == NULL)
	ds = ds_init(&d);
  else
	ds = *url_dsp = ds_init(NULL);

  nonp = 0;
  if (non_printable != NULL)
	*non_printable = -1;

  for (p = url; *p != '\0'; p++) {
    if (*p == '%') {
	  p++;
	  if (*p == '\0' || *(p + 1) == '\0' || (ch = hexpair2int(p)) == -1) {
		if (url_dsp != NULL)
		  *url_dsp = NULL;
		return(NULL);
	  }
	  p++;
    }
	else
	  ch = *p;

	if (ch == '\0')
	  nonp++;

	ds_appendc(ds, ch);
  }
  ds_appendc(ds, '\0');

  if (non_printable != NULL)
	*non_printable = nonp;

  if (nonp)
	return(NULL);

  return(ds_buf(ds));
}

static char *url_chars
= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;/?:@&=+$,-_.!~*'()%";

/*
 * RFC 3986 (2.1, 6.2.2.1) says URI producers and normalizers should use
 * uppercase hexadecimal digits for all percent-encodings.
 */
static void
c2x(Ds *ds, unsigned int what)
{
  static const char c2x_table[] = "0123456789ABCDEF";

  ds_appendc(ds, (int) '%');
  ds_appendc(ds, (int) c2x_table[what >> 4]);
  ds_appendc(ds, (int) c2x_table[what & 0xf]);
}

char *
percent_encode_chars(char *url, char *chars_to_escape, size_t url_len)
{
  int i;
  unsigned int ch;
  char *s;
  size_t len;
  Ds ds;

  if (url_len == 0)
	len = strlen(url);
  else
	len = url_len;

  ds_init(&ds);
  s = url;
  for (i = 0; i < len; i++) {
	ch = *s++ & 0377;
	/* A null character will satisfy both tests, so it will be escaped. */
	if (strchr(url_chars, ch) == NULL || strchr(chars_to_escape, ch) != NULL)
	  c2x(&ds, ch);
	else
	  ds_appendc(&ds, ch);
  }
  ds_appendc(&ds, '\0');

  return(ds_buf(&ds));
}

char *
percent_encode_other_chars(char *url, char *chars_not_to_escape, size_t url_len)
{
  int i;
  unsigned int ch;
  char *s;
  size_t len;
  Ds ds;

  if (url_len == 0)
	len = strlen(url);
  else
	len = url_len;

  ds_init(&ds);
  s = url;
  for (i = 0; i < len; i++) {
	ch = *s++ & 0377;
	if (strchr(chars_not_to_escape, ch) != NULL)
	  ds_appendc(&ds, ch);
	else
	  c2x(&ds, ch);
  }
  ds_appendc(&ds, '\0');

  return(ds_buf(&ds));
}

/*
 * Return the URL encoding of URL, which is null-terminated if
 * LEN == 0, otherwise it is of length LEN.
 */
char *
url_encode(char *url, size_t len)
{
  static char *excluded = " <>#%<{}|\\^[]`;/?:@&=+$,";

  return(percent_encode_chars(url, excluded, len));
}

int
contains_invalid_url_char(char *str)
{

  if (strspn(str, url_chars) != strlen(str))
	return(1);

  return(0);
}
