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

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 *
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * HTTP command and support functions
 *
 * SSL (HTTPS) connections are made by filtering communication with the
 * web server through sslclient (or stunnel):
 *
 *    HTTP <----> sslclient/stunnel <------> web server
 *         (pipe)                   (tcp/ip)
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: http.c 2861 2015-12-28 22:17:08Z brachman $";
#endif

#include "dacs_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "local.h"
#include "http.h"

static MAYBE_UNUSED char *log_module_name = "http";

#ifndef PROG

int is_http_prog = 0;

/*
 * RFC 2616 3.1, RFC 2145
 * We need to be "1.0" because we don't implement transfer codings (3.6).
 *
 * All HTTP/1.1 applications MUST be able to receive and decode the
 * "chunked" transfer-coding, and MUST ignore chunk-extension extensions
 * they do not understand. (3.6.1, 4.4)
 *
 * To allow for transition to absoluteURIs in all requests in future
 * versions of HTTP, all HTTP/1.1 servers MUST accept the absoluteURI
 * form in requests, even though HTTP/1.1 clients will only generate
 * them in requests to proxies. (5.1.2)
 *
 * The Host request-header field (section 14.23) MUST accompany all
 * HTTP/1.1 requests. (9, 14.23)
 *
 * HTTP/1.1 applications that do not support persistent connections MUST
 * include the "close" connection option in every message. (14.10)
 */
/* The highest HTTP version with which this is compliant. */
#define HTTP_VERSION "1.0"
#define HTTP_VERSION_MAJOR	1
#define HTTP_VERSION_MINOR	0

static int use_authorization_header = 0;
static char *user_agent_string = NULL;
static int verbose_flag = 0;

/*
 * Add an HTTP parameter called NAME with value VALUE (which may be NULL,
 * in which case FILENAME is where the value will eventually be read from).
 * If ENCODED is non-zero, then the value has already been appropriately
 * encoded.
 * Note that if NAME is NULL, then VALUE already has the format NAME=VALUE.
 * See send_request().
 */
Http_params *
http_param(Dsvec *dsv_params, char *name, char *value, char *filename,
		   int encoded)
{
  Http_params *params;

  params = dsvec_new_obj(dsv_params, Http_params *);
  params->name = name;
  params->value = value;
  params->filename = filename;
  params->encoded = encoded;

  return(params);
}

void
http_set_post_params(Http *h, int n, Http_params *p)
{

  h->npost_params = n;
  h->post_params = p;
}

void
http_set_ssl_params(Http *h, Dsvec *v)
{

  if (h->ssl_args != NULL)
	dsvec_free(h->ssl_args);

  if (v == NULL || dsvec_len(v) == 0)
	h->ssl_args = NULL;
  else {
	int i;
	char *a;
	Ds ds;

	/*
	 * XXX this is a crock because it assumes that sslclient is being used
	 * support for other SSL programs should probably be removed
	 */
	if (is_http_prog)
	  dsvec_insert_ptr(v, 1, "-un");
	h->ssl_args = v;

	ds_init(&ds);
	for (i = 0; i < dsvec_len(v); i++) {
	  a = (char *) dsvec_ptr_index(v, i);
	  if (a == NULL)
		break;
	  ds_asprintf(&ds, "%s\"%s\"", (i == 0) ? "" : " ", a);
	}
	log_msg((LOG_TRACE_LEVEL, "SSL flags: %s", ds_buf(&ds)));
  }
}

/*
 * If ARGV is NULL, reset the cookie list to empty, otherwise add
 * the cookies to the current list.
 */
void
http_set_cookies(Http *h, char **argv)
{
  int i;

  if (argv == NULL) {
	if (h->cookie_args != NULL) {
	  dsvec_free(h->cookie_args);
	  h->cookie_args = NULL;
	}
	return;
  }

  /* If there's no cookie, there's nothing to add. */
  if (argv[0] == NULL)
	return;

  if (h->cookie_args == NULL)
	h->cookie_args = dsvec_init(NULL, sizeof(char *));

  for (i = 0; argv[i] != NULL; i++)
	dsvec_add_ptr(h->cookie_args, argv[i]);

  log_msg((LOG_TRACE_LEVEL, "Set %d cookie%s", i, i > 1 ? "s" : ""));
}

/*
 * This is used to build the Request-URI for a Request-Line.
 */
static char *
build_uri_path(Uri *uri)
{
  int i;
  Ds ds;
  Uri_query_arg *a;

  ds_init(&ds);
  if (uri->path != NULL)
	ds_asprintf(&ds, "%s", percent_encode_chars(uri->path, "%;?&", 0));
  else
	ds_appendc(&ds, (int) '\0');

  for (i = 0; i < dsvec_len(uri->query_args); i++) {
	a = (Uri_query_arg *) dsvec_ptr_index(uri->query_args, i);
	if (a->value != NULL)
	  ds_asprintf(&ds, "%s%s=%s", (i == 0) ? "?" : "&",
				  percent_encode_chars(a->name, "%;?&=", 0),
				  percent_encode_chars(a->value, "%;?&=", 0));
	else
	  ds_asprintf(&ds, "%s%s", (i == 0) ? "?" : "&",
				  percent_encode_chars(a->name, "%;?&=", 0));
  }

  return(ds_buf(&ds));
}

static char *
append_header_line(char *str, char *append)
{
  char *nstr;

  if (str == NULL)
	nstr = strdup(append);
  else
	nstr = ds_xprintf("%s %s", str, append);

  return(nstr);
}

static char *
append_header_field(char *str, char *append)
{
  char *nstr;

  if (str == NULL)
	nstr = strdup(append);
  else
	nstr = ds_xprintf("%s,%s", str, append);

  return(nstr);
}

static char *
add_response_kwv(Http *h, char *header)
{
  char *key, *line, *p, *value;
  Kwv_pair *pair;

  if (h->response_kwv == NULL || header == NULL)
	return(NULL);

  h->response_kwv->icase = 1;
  h->response_kwv->dup_mode = KWV_NO_DUPS;

  line = strdup(header);
  if ((p = strchr(line, (int) ':')) == NULL)
	return(NULL);

  *p++ = '\0';
  key = line;
  while (*p == ' ' || *p == '\t')
	p++;
  value = p;

  if ((pair = kwv_lookup(h->response_kwv, key)) == NULL) {
	kwv_add_nocopy(h->response_kwv, key, value);
	return(value);
  }

  pair->val = append_header_field(pair->val, value);

  return(pair->val);
}

void (*__cleanup)(void);

/*
 * Parse the Status-Line and response headers in the server's
 * HTTP response message (RFC 2616, 6); stop before reading the message body.
 * Return 0 if ok, -1 on error.
 */
static int
parse_response(Http *h)
{
  char *code, *line, *p, *p2, *pline, *reason, *status_line, *version;
  Ds response;

  log_msg((LOG_TRACE_LEVEL, "Reading status line..."));

  if (h->fp == NULL) {
	int sock;

	sock = (h->ssl_args != NULL) ? h->ssl_read_fd : h->sd;
	if ((h->fp = fdopen(sock, "r")) == NULL) {
	  log_err((LOG_ERROR_LEVEL, "parse_response: fdopen"));
	  return(-1);
	}
  }

  ds_init(&response);
  response.len_limit = HTTP_MAX_RESPONSE_HEADER_LEN;
  response.delnl_flag = 1;
  response.crnl_flag = 1;

  while (1) {
	if ((status_line = ds_gets(&response, h->fp)) == NULL) {
	  if (ferror(h->fp)) {
		log_msg((LOG_ERROR_LEVEL, "error reading response headers"));
		return(-1);
	  }
	  if (feof(h->fp)) {
		log_msg((LOG_ERROR_LEVEL, "EOF looking for status line"));
		return(-1);
	  }
	  if (ds_errmsg != NULL) {
		log_msg((LOG_ERROR_LEVEL, "%s", ds_errmsg));
		return(-1);
	  }
	}
	if (status_line[0] != '\0')
	  break;
  }

  h->status_line = strdup(status_line);

  /*
   * Parse the status line (RFC 2616, 6.1):
   *  Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
   *  HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
   *  The Status-Code element is a 3-digit integer result code.
   *  The Reason-Phrase is intended to give a short textual description of
   *  the Status-Code.
   */
  line = strdup(status_line);
  if ((p = strchr(line, '/')))
	*p++ = '\0';
  if (p == NULL || !streq(line, "HTTP"))
	return(-1);

  if ((p2 = strpbrk(p, " \t")) != NULL) {
	*p2++ = '\0';
	version = p;
	/* Skip past the version number */
	p2 += strspn(p2, " \t");
  }
  if (p2 == NULL)
	return(-1);
  p = code = p2;

  /* XXX Check HTTP version */
  if ((p2 = strpbrk(code, " \t")))
	*p2++ = '\0';
  reason = p2;

  /* Check and get status code */
  if (!isdigit((int) p[0]) || !isdigit((int) p[1])
	  || !isdigit((int) p[2]) || p[3] != '\0') {
	h->status_code = -1;
	return(-1);
  }
  if (strnum(code, STRNUM_I, &h->status_code) == -1)
	return(-1);

  /*
   * Skip response-header fields that follow, up to and including an
   * empty line (RFC 2616, 4.2, 6.2, 7.1)
   * LWS may be replaced by a single space
   * LWS -> [CRLF] 1*( SP | HT )
   */
  ds_reset(&response);
  line = NULL;
  while (1) {
	if ((pline = ds_gets(&response, h->fp)) == NULL) {
	  if (ferror(h->fp)) {
		log_msg((LOG_ERROR_LEVEL, "error reading response headers"));
		return(-1);
	  }

	  if (feof(h->fp)) {
		log_msg((LOG_ERROR_LEVEL, "EOF reading response headers"));
		return(-1);
	  }

	  /* If a line is too long it may be truncated -- ignore it. */
	}
	else {
	  /*
	   * RFC 2616, 4.2:
	   * o Field names are case-insensitive. The field value MAY be preceded
	   *   by any amount of LWS, though a single SP is preferred. Header
	   *   fields can be extended over multiple lines by preceding each
	   *   extra line with at least one SP or HT."
	   * o The field-content does not include any leading or trailing LWS:
	   *   linear white space occurring before the first non-whitespace
	   *   character of the field-value or after the last non-whitespace
	   *   character of the field-value. Such leading or trailing LWS MAY be
	   *   removed without changing the semantics of the field value. Any LWS
	   *   that occurs between field-content MAY be replaced with a single SP
	   *   before interpreting the field value or forwarding the message
	   *   downstream.
	   * o The order in which header fields with differing field names are
	   *   received is not significant.
	   * o Multiple message-header fields with the same field-name MAY be
	   *   present in a message if and only if the entire field-value for
	   *   that header field is defined as a comma-separated list
	   *   [i.e., #(values)].  It MUST be possible to combine the multiple
	   *   header fields into one "field-name: field-value" pair, without
	   *   changing the semantics of the message, by appending each
	   *   subsequent field-value to the first, each separated by a comma.
	   *   The order in which header fields with the same field-name are
	   *   received is therefore significant to the interpretation of the
	   *   combined field value [...]"
	   */
	  if (*pline == ' ' || *pline == '\t') {
		/* This is a continuation of the previous line... */
		do {
		  pline++;
		} while (*pline == ' ' || *pline == '\t');

		if (line != NULL) {
		  line = append_header_line(line, pline);
		  continue;
		}
		log_msg((LOG_ERROR_LEVEL, "invalid continued line"));
		return(-1);
	  }

	  if (line != NULL && *line != '\0') {
		if (h->response_headers != NULL)
		  dsvec_add_ptr(h->response_headers, strdup(line));
		if (h->response_kwv != NULL)
		  add_response_kwv(h, line);

		if (h->method == HTTP_HEAD_METHOD || h->method == HTTP_OPTIONS_METHOD)
		  log_msg((LOG_TRACE_LEVEL, "%s", line));
	  }

	  if (*pline == '\0')
		break;
	  line = strdup(pline);
	}
  }

  if (h->response_headers == NULL)
	ds_free(&response);

  return(0);
}

Uri *
http_parse_uri(char *str)
{
  Uri *uri;

  if ((uri = uri_parse(str)) == NULL)
	return(NULL);

  if (uri->scheme == NULL)
	return(NULL);

  if (uri->port_given != NULL && uri->port == 0)
	return(NULL);

  /* Scheme is case insensitive.  RFC 2396 S3.1 */
  if (strcaseeq(uri->scheme, "http")) {
	if (uri->port_given == NULL)
	  uri->port = HTTP_DEFAULT_PORT;
  }
  else if (strcaseeq(uri->scheme, "https")) {
	if (uri->port_given == NULL)
	  uri->port = HTTP_DEFAULT_SSL_PORT;
  }
  else
	return(NULL);

  return(uri);
}

Uri *
uri_init(Uri *u)
{
  Uri *uri;

  if (u == NULL)
	uri = ALLOC(Uri);
  else
	uri = u;

  uri->uri = NULL;
  uri->scheme = NULL;
  uri->authority = NULL;
  uri->host = NULL;
  uri->host_is_dn = 0;
  uri->host_is_ip = 0;
  uri->userinfo = NULL;
  uri->port = 0;
  uri->port_given = NULL;
  uri->server = 0;
  uri->path = NULL;
  uri->path_parts = NULL;
  uri->query_string = NULL;
  uri->query_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
  uri->fragment = NULL;

  return(uri);
}

#ifdef NOTDEF
static int
hexchar_val(int ch)
{

  if (ch >= '0' && ch <= '9')
	return(ch - '0');
  else if (ch >= 'a' && ch <= 'f')
	return(ch - (int) 'a' + 10);
  else if (ch >= 'A' && ch <= 'F')
	return(ch - (int) 'A' + 10);
  else
	return(-1);
}

static char *
decode_hexpair(char *str, int *val)
{

  if (isxdigit((int) *str) && isxdigit((int) *(str + 1))) {
	if (val)
	  *val = hexchar_val((int) *str) * 16
		+ hexchar_val((int) *(str + 1));
	return(str + 2);
  }

  return(NULL);
}
#endif

/*
 * Given PATH, the path part of an encoded URL (with or without a leading
 * '/'), break it into its slash delimited components.  Terminate the list
 * with a NULL entry.
 * Return NULL if a decoding error occurs.
 */
Dsvec *
uri_path_parse(char *path)
{
  char *p, *s;
  Ds *ds;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(Ds *));

  if (path == NULL) {
	dsvec_add_ptr(dsv, NULL);
	return(dsv);
  }
	
  if (*path == '/')
	path++;

  for (p = s = strdup(path); *p != '\0'; p++) {
	if (*p == '/') {
	  *p++ = '\0';
	  if (url_decode(s, NULL, &ds) == NULL)
		return(NULL);
	  dsvec_add_ptr(dsv, ds);
	  s = p;
	}
  }

  if (*s != '\0') {
	if (url_decode(s, NULL, &ds) == NULL)
	  return(NULL);
	dsvec_add_ptr(dsv, ds);
  }

  dsvec_add_ptr(dsv, NULL);

  return(dsv);
}

/*
 * Extract the path, query, and fragment part of URI (all optionally) but
 * do NOT decode them.
 * Return 0 if ok, or -1 if an error occurs.
 */
int
uri_pqf(char *uri_str, char **path_p, char **query_p, char **fragment_p)
{
  char *fragment, *p, *path, *query, *xuri;

  if (uri_str == NULL)
	return(-1);

  /* Carve up a copy. */
  xuri = strdup(uri_str);

  path = query = fragment = NULL;

  if (*xuri != '?' && *xuri != '#' && *xuri != '\0')
	path = xuri;

  if ((p = strchr(xuri, (int) '?')) != NULL) {
	*p++ = '\0';
	xuri = query = p;
  }

  if ((p = strchr(xuri, (int) '#')) != NULL)
	fragment = p + 1;

  if (path_p != NULL)
	*path_p = path;
  if (query_p != NULL)
	*query_p = query;
  if (fragment_p != NULL)
	*fragment_p = fragment;

  return(0);
}

/*
 * Look for a syntactically valid scheme in STR (destructively).
 * If found, set appropropriate fields in URI and return a pointer to the
 * start of the next URI component; otherwise, return NULL.
 */
static char *
uri_parse_scheme(Uri *uri, char *str)
{
  char *p, *q;

  /*
   * The scheme is an alphabetic followed by any number of alphanumerics
   * or "+", "-", "." characters.  A ":" separates it from the parts that
   * follow.
   */
  if ((p = strchr(str, ':' )) == NULL || p == str)
	return(NULL);

  *p++ = '\0';
  
  if (!isalpha((int) *str))
	return(NULL);
  for (q = str + 1; *q != '\0'; q++) {
	if (isupper((int) *q))
	  *q = tolower((int) *q);
	else if (!isalnum((int) *q) && *q != '+' && *q != '-' && *q != '.')
	  return(NULL);
  }

  uri->scheme = str;

  return(p);
}

/*
 * RFC 3986 (3.2):
 *  The authority component is preceded by a double slash ("//") and is
 *  terminated by the next slash ("/"), question mark ("?"), or number
 *  sign ("#") character, or by the end of the URI.
 *    authority   = [ userinfo "@" ] host [ ":" port ]
 *  If a URI contains an authority component, then the path component
 *  must either be empty or begin with a slash ("/") character.
 *
 * A hostname/IP address is expected.
 * If the optional port number is present, it is preceded by a colon.
 * First, advance to the path part, which is optional.
 */
static char *
uri_parse_authority(Uri *uri, char *str)
{
  char *auth, *auth_end, *hp, *p, *q, *userinfo_end;
  Ds ds;

  if ((auth_end = strpbrk(str, "/?#")) != NULL)
	auth = strndup(str, auth_end - str);
  else {
	auth = strdup(str);
	auth_end = "";
  }

  if ((userinfo_end = strchr(auth, (int) '@')) != NULL) {
	p = strndup(auth, userinfo_end - auth);
	uri->userinfo = url_decode(p, NULL, NULL);
	hp = userinfo_end + 1;
  }
  else
	hp = strdup(auth);

  /* XXX No IP-literal term allowed */

  /* Extract the IPv4address or hostname, and optional port number. */
  if ((p = strchr(hp, (int) ':')) != NULL) {
	/* There's a port. */
	*p++ = '\0';
	uri->port_given = p;
	if (strnum(p, STRNUM_IN_PORT_T, &uri->port) == -1)
	  return(NULL);
  }

  /* Must check for IP address first. */
  if (is_ip_addr(hp, NULL)) {
	uri->host = hp;
	uri->host_is_ip = 1;
  }
  else {
	int saw_percent, val;

	/*
	 * A reg-name consisting of:
	 *   ALPHA / DIGIT / "-" / "." / "_" / "~" / pct-encoded /
	 *   "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
	 * Note than a zero-length reg-name is valid, as in the "file" scheme.
	 * Also note that it is case insensitive.
	 * RFC 3986 (2.4, 7.3)
	 */
	saw_percent = 0;
	for (q = hp; *q != '\0'; q++) {
	  static char *valid_reg_name_chars = "-._~!$&'()*+,;=";

	  if (isupper((int) *q))
		*q = tolower((int) *q);
	  else if (*q == '%') {
		if ((val = hexpair2int(q + 1)) == -1)
		  return(NULL);
		if (val == 0)	/* Do not allow a NUL! */
		  return(NULL);
		saw_percent = 1;
		q += 2;
	  }
	  else if (!isalnum((int) *q)
			   && strchr(valid_reg_name_chars, (int) *q) == NULL)
		return(NULL);
	}
	if (saw_percent)
	  p = url_decode(hp, NULL, NULL);
	else
	  p = hp;

	uri->host = p;
	uri->host_is_dn = looks_like_domain_name(uri->host);
  }

  /*
   * Create some convenient strings from the decoded values.
   */
  ds_init(&ds);
  if (uri->userinfo != NULL)
	ds_asprintf(&ds, "%s@", uri->userinfo);
  ds_asprintf(&ds, "%s", uri->host);
  if (uri->port_given != NULL)
	ds_asprintf(&ds, ":%s", uri->port_given);
  uri->authority = ds_buf(&ds);

  if (uri->host_is_dn || uri->host_is_ip) {
	if (uri->port_given != NULL)
	  uri->server = ds_xprintf("%s:%s", uri->host, uri->port_given);
	else
	  uri->server = strdup(uri->host);
  }

  return(auth_end);
}

int
uri_is_valid_path_part(char *part)
{
  char *s;
  static char *valid_path_chars = ":@!$&'()*+,;=-._~";

  if (*part == '/')
	s = part + 1;
  else
	s = part;

  while (*s != '\0') {
	if (isalnum((int) *s)) {
	  s++;
	  continue;
	}

	if (strchr(valid_path_chars, (int) *s) != NULL) {
	  s++;
	  continue;
	}

	if (*s == '%' && hexpair2int(s + 1) != -1) {
	  s += 3;
	  continue;
	}

	return(0);
  }

  return(1);
}

/*
 * Break the path component of a URI (the query and/or fragment component
 * having already been removed) into individual segments, then assemble the
 * URL decoded version of the path.
 *
 * Note that zero-length segments are permitted.
 *
 * Syntax of each path segment is:
 *   unreserved / pct-encoded / sub-delims / ":" / "@"
 * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
 *                  / "*" / "+" / "," / ";" / "="
 * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
 *
 * If there is no path, return 0; if there is an error, return -1; if there
 * is a valid path, return the number of elements in the path, and set
 * URI->path to the path.
 */
int
uri_make_path(Uri *uri, char *str)
{
  int i;
  char *decoded_part;
  Ds ds;

  /* No path string at all? */
  if (str == NULL || *str == '\0')
	return(0);

  /* A string without elements? */
  if ((uri->path_parts = strsplitd(str, "/", 0, 1)) == NULL)
	return(-1);

  ds_init(&ds);
  for (i = 0; i < dsvec_len(uri->path_parts); i++) {
	char *part;

	part = (char *) dsvec_ptr_index(uri->path_parts, i);
	if (!uri_is_valid_path_part(part))
	  return(-1);

	if ((decoded_part = url_decode(part, NULL, NULL)) == NULL)
	  return(-1);

	ds_asprintf(&ds, "%s", decoded_part);
  }
  uri->path = ds_buf(&ds);

  return(dsvec_len(uri->path_parts));
}

/*
 * Look for a syntactically valid "hier-part" component of a URI in STR.
 * If HAS_AUTHORITY is non-zero an initial "//" was seen and STR advanced to
 * the next character.
 *
 * Return a pointer to the start of the next component (query or fragment)
 * in STR, if any, otherwise the empty string; if an error occurs return NULL.
 *
 * RFC 2396, Appendix A (and RFC 3986):
 *   absoluteURI = scheme ":" ( hier_part | opaque_part )
 *   hier_part = ( net_path | abs_path ) [ "?" query ] [ "#" fragment ]
 *   opaque_part   = uric_no_slash *uric
 *   net_path = "//" authority [ abs_path ]
 *   abs_path = "/"  path_segments
 *   authority = server | reg_name
 *   server = [ [ userinfo "@" ] hostport ]
 *   reg_name = 1*( unreserved | escaped | "$" | "," |
 *                  ";" | ":" | "@" | "&" | "=" | "+" )
 *   path_segments = segment *( "/" segment )
 *   segment = *pchar *( ";" param )
 *   param = *pchar
 *   pchar = unreserved | escaped |
 *           ":" | "@" | "&" | "=" | "+" | "$" | ","
 *   query = *uric
 *   uric = reserved | unreserved | escaped
 *   reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
 *              "$" | ","
 *   unreserved = alphanum | mark
 *   mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
 *   escaped = "%" hex hex
 *   hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
 *         "a" | "b" | "c" | "d" | "e" | "f"
 */
char *
uri_parse_hier_part(Uri *uri, char *str, int has_authority)
{
  char *p, *q, *s;

  s = p = strdup(str);

  if (has_authority) {
	/*
	 * Parse the authority component and advance to the following character,
	 * which may be a slash ("/"), question mark ("?"), number
	 * sign ("#"), or the end of the URI ("").
	 */
	if ((p = uri_parse_authority(uri, p)) == NULL)
	  return(NULL);
  }

  /* Parse the remainder, if any. */
  if (*p == '\0')
	return("");

  /*
   * If there is a query or fragment identifier component, terminate the
   * path component.
   */
  if ((q = strchr(p, (int) '?')) != NULL)
	*q = '\0';
  else if ((q = strchr(p, (int) '#')) != NULL)
	*q = '\0';

  if (uri_make_path(uri, p) == -1)
	return(NULL);

  if (q == NULL)
	return("");

  return(str + (q - s));
}

/*
 * This is a partial implementation of RFC 2396/3986:
 *  URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
 *  hier-part = "//" authority path-abempty
 *                / path-absolute / path-rootless / path-empty
 *
 *   The scheme and path components are required, though the path may be
 *   empty (no characters).  When authority is present, the path must
 *   either be empty or begin with a slash ("/") character.  When
 *   authority is not present, the path cannot begin with two slash
 *   characters ("//").
 */
Uri *
uri_parse(char *str)
{
  char *frag, *p, *q, *u;
  Uri *uri;

  uri = uri_init(NULL);
  uri->uri = strdup(str);

  u = strdup(str);

  if ((p = uri_parse_scheme(uri, u)) == NULL)
	return(NULL);

  /*
   * RFC 3986:
   * The (optional) authority component is preceded by a double slash ("//")
   * and is terminated by the next slash ("/"), question mark ("?"), or number
   * sign ("#") character, or by the end of the URI.
   * RFC 2396:
   * The net_path (two slashes) or abs_path (one slash) part follows.
   */
  if (*p == '/' && *(p + 1) == '/')
	q = uri_parse_hier_part(uri, p + 2, 1);
  else
	q = uri_parse_hier_part(uri, p, 0);

  if (q == NULL)
	return(NULL);
  if (*q == '\0')
	return(uri);

  /*
   * There is a query component, a fragment component, or both.
   */
  if (*q == '#')
	frag = q + 1;
  else if (*q == '?') {
	q++;
	if ((frag = strchr(q, (int) '#')) != NULL)
	  *frag++ = '\0';

	uri->query_string = strdup(q);
	if ((uri->query_args = parse_query_string(q)) == NULL)
	  return(NULL);
  }
  else
	frag = NULL;

  if (frag != NULL)
	uri->fragment = url_decode(frag, 0, 0);

  return(uri);
}

char *
uri_scheme_authority(Uri *uri)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "%s://%s", uri->scheme, uri->host);
  if (uri->port_given != NULL)
	ds_asprintf(&ds, ":%s", uri->port_given);

  return(ds_buf(&ds));
}

static int
strequiv(char *a, char *b)
{

  if (a == NULL && b == NULL)
	return(1);
  if ((a == NULL && b != NULL)
	  || (a != NULL && b == NULL))
	return(0);
  return(streq(a, b));
}

static int
strcaseequiv(char *a, char *b)
{

  if (a == NULL && b == NULL)
	return(1);
  if ((a == NULL && b != NULL)
	  || (a != NULL && b == NULL))
	return(0);
  return(strcaseeq(a, b));
}

/*
 * Return 1 if two URIs, URI1 and URI2, are equivalent, and 0 if not.
 * If MATCH is non-zero, set it to the number of matching path components.
 */
int
uri_compare(Uri *uri1, Uri *uri2, int *match)
{
  int n, st;

  st = 1;
  n = 0;
  if (!strcaseequiv(uri1->scheme, uri2->scheme))
	st = 0;
  else if (!strcaseequiv(uri1->host, uri2->host))
	st = 0;
  else if (!strequiv(uri1->port_given, uri2->port_given))
	st = 0;
  else if (!strequiv(uri1->userinfo, uri2->userinfo))
	st = 0;
  else if (!strequiv(uri1->fragment, uri2->fragment))
	st = 0;
  else {
	char *seg1, *seg2;

	while (1) {
	  if (uri1->path_parts == NULL)
		seg1 = NULL;
	  else
		seg1 = (char *) dsvec_ptr_index(uri1->path_parts, n);

	  if (uri2->path_parts == NULL)
		seg2 = NULL;
	  else
		seg2 = (char *) dsvec_ptr_index(uri2->path_parts, n);

	  if (seg1 == NULL || seg2 == NULL || !streq(seg1, seg2))
		break;
	  n++;
	}

	if (seg1 == NULL && seg2 == NULL)
	  st = 1;
	else
	  st = 0;
  }

  if (match != NULL)
	*match = n;

  return(st);
}

char *
uri_to_str(Uri *uri)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "%s/", uri_scheme_authority(uri));
  if (uri->path != NULL)
	ds_asprintf(&ds, "%s", uri->path);
  if (uri->query_string != NULL)
	ds_asprintf(&ds, "?%s", url_encode(uri->query_string, 0));
  if (uri->fragment != NULL)
	ds_asprintf(&ds, "#%s", url_encode(uri->fragment, 0));

  return(ds_buf(&ds));
}

char *
http_get_user_agent_string(void)
{

  return(user_agent_string);
}

char *
http_set_user_agent_string(char *str)
{

  if (str == NULL && user_agent_string == NULL)
	user_agent_string = ds_xprintf("DACS-http/%s", dacs_version_release);
  else if (str != NULL)
	user_agent_string = strdup(str);

  return(user_agent_string);
}

/*
 * Connect to PORT on SERVER.
 * Return 0 if successful, -1 otherwise.
 */
static int
connect_to_server(Http *h, char *server, in_port_t port)
{
  int gai_code, rc, sd;
  char *port_str;
  struct addrinfo ai_hints, *ai;

  if (h->ssl_args != NULL) {
	char *p;
	Dsvec *dsv;

	/*
	 * Be careful about what gets passed in the environment, since it may
	 * be visible to all.
	 */
	dsv = dsvec_init(NULL, sizeof(char *));
	if ((p = getenv("REMOTE_ADDR")) != NULL)
	  dsvec_add_ptr(dsv, ds_xprintf("REMOTE_ADDR=%s", p));
	dsvec_add_ptr(dsv, NULL);
	h->ssl_env = (char **) dsvec_base(dsv);

	rc = filterthru((char **) dsvec_base(h->ssl_args), h->ssl_env,
					&h->ssl_read_fd, &h->ssl_write_fd, &h->ssl_error_fd, NULL);
	if (rc == -1) {
	  log_msg((LOG_ERROR_LEVEL, "SSL filter failed"));
	  return(-1);
	}

	return(0);
  }

  memset(&ai_hints, 0, sizeof(ai_hints));
  ai_hints.ai_family = AF_INET;
  ai_hints.ai_socktype = SOCK_STREAM;
  ai_hints.ai_protocol = IPPROTO_TCP;
  ai_hints.ai_flags = AI_CANONNAME;

  ai = NULL;
  port_str = ds_xprintf("%u", (unsigned int) port);
  gai_code = getaddrinfo(server, port_str, &ai_hints, &ai);
  if (gai_code != 0) {
	log_msg((LOG_ERROR_LEVEL, "getaddrinfo failed for '%s'", server));
	return(-1);
  }

  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
	log_err((LOG_ERROR_LEVEL, "socket() failed"));
	return(-1);
  }

  if (connect(sd, ai->ai_addr, ai->ai_addrlen) == -1) {
	log_err((LOG_ERROR_LEVEL, "connect() failed"));
	close(sd);
	freeaddrinfo(ai);
	return(-1);
  }
  freeaddrinfo(ai);

  log_msg((LOG_TRACE_LEVEL, "Connected to %s:%u", server, port));

  h->sd = sd;
  return(0);
}

static int
write_to_server(Http *h, char *data, size_t length)
{
  int sock;
  size_t nleft;

  sock = (h->ssl_args != NULL) ? h->ssl_write_fd : h->sd;

  if (h->ssl_args != NULL) {
	static int need_init = 1;

	if (need_init) {
	  struct timeval tv;

	  /*
	   * XXX When we're using SSL via stunnel and therefore writing to a pipe
	   * rather than directly to the web server, for some
	   * reason it is necessary to insert a slight delay before writing.
	   * If this isn't done, it seems that communication over the pipe fails.
	   * I haven't been able to figure out why that is... maybe it's
	   * peculiar to stunnel and how it starts up.  The current 0.1 sec
	   * delay was a low value that solved that problem; adjust it if the
	   * problem recurs.
	   * - bjb 21-May-03
	   */
	  tv.tv_sec =  0;
	  tv.tv_usec = 100000;
	  select(0, NULL, NULL, NULL, &tv);
	}
	need_init++;
  }

  nleft = length;
  while (nleft > 0) {
	ssize_t nwritten;

	nwritten = write(sock, (const void *) data, nleft);
	if (nwritten == -1) {
	  if (errno == EINTR)
		continue;
	  if (errno == EAGAIN) {
		struct timeval tv;

		tv.tv_sec =  0;
		tv.tv_usec = 50000;
		select(0, NULL, NULL, NULL, &tv);
		continue;
	  }
	  if (errno == EPIPE && h->ssl_write_fd != -1) {
		/* SSL process quit, probably.  A message ought to be superfluous... */
		return(-1);
	  }
	  log_err((LOG_ERROR_LEVEL, "write_to_server() write error"));
	  return(-1);
	}

	log_msg((LOG_TRACE_LEVEL, "Wrote %ld bytes", (long) nwritten));

	nleft -= nwritten;
	data += nwritten;
  }

  return(0);
}

static void
http_start_data(Http *h, Ds *body, size_t len)
{

  if (!h->in_data) {
	if (write_to_server(h, "\r\n", 2) != 0)
	  log_msg((LOG_ERROR_LEVEL, "Write error"));
	h->in_data = 1;
  }

  if (body == NULL || len == 0)
	return;

  log_msg(((Log_level) (LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG),
		   "Send the message (%d bytes):\n%s", (long) len, ds_buf(body)));

  if (len && write_to_server(h, ds_buf(body), len) != 0)
	log_msg((LOG_ERROR_LEVEL, "Write error"));
}

/*
 * Send an HTTP request to the server.
 * Return 0 if successful, -1 otherwise.
 */
static int
send_request(Http *h)
{
  int i, rc, sent_authorization; 
  char *p, *pre_path, *server;
  size_t body_len;
  in_port_t port;
  Ds *body, request;

  sent_authorization = 0;

  if (h->uri->host == NULL || *h->uri->host == '\0')
	server = "localhost";
  else
	server = h->uri->host;
  port = h->uri->port ? h->uri->port : HTTP_DEFAULT_PORT;

  if (h->proxy.hostname != NULL) {
	rc = connect_to_server(h, h->proxy.hostname, h->proxy.portnum);
	if (rc == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not connect to server %s:%u\n",
			   h->proxy.hostname, h->proxy.portnum));
	  return(-1);
	}
  }
  else {
	rc = connect_to_server(h, server, port);
	if (rc == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not connect to server %s:%u\n",
			   server, port));
	  return(-1);
	}
  }

  p = build_uri_path(h->uri);

  /* (5.1) */
  if (h->method == HTTP_OPTIONS_METHOD && h->server_options) {
	p = "*";
	pre_path = "";
  }
  else
	pre_path = (*p == '/') ? "" : "/";

  ds_init(&request);
  ds_asprintf(&request, "%s %s%s%s HTTP/%s\r\n",
			  http_method_to_string(h->method),
			  (h->proxy.hostname != NULL) ? uri_scheme_authority(h->uri) : "",
			  pre_path, p,
			  HTTP_VERSION);
  /* (14.23) */
  ds_asprintf(&request, "Host: %s:%u\r\n", h->uri->host, h->uri->port);
  /* (14.43) */
  ds_asprintf(&request, "User-Agent: %s\r\n", user_agent_string);
  /* (14.10) */
  ds_asprintf(&request, "Connection: close\r\n");
  /*
   * Clients SHOULD only send a Date header field in messages that include
   * an entity-body, as in the case of the PUT and POST requests, and even
   * then it is optional. (14.18)
   */

  for (i = 0; i < dsvec_len(h->message_headers); i++) {
	p = (char *) dsvec_ptr_index(h->message_headers, i);
	ds_asprintf(&request, "%s\r\n", p);
  }

  /*
   * If proxy authentication has been pre-selected, send it.
   */
  if (h->proxy_userinfo != NULL) {
	char *basic_credentials;

	mime_encode_base64((unsigned char *) h->proxy_userinfo,
					   strlen(h->proxy_userinfo), &basic_credentials);
	ds_asprintf(&request, "Proxy-Authorization: Basic %s\n", basic_credentials);
	/* XXX the caller should zap h->proxy_userinfo */
	log_msg((LOG_DEBUG_LEVEL, "Proxy-Authorization: Basic %s",
			 basic_credentials));
	strzap(basic_credentials);
  }

  /*
   * Send the userinfo field as an Authorization header, but only if not
   * already done.
   * XXX HTTP Digest is not supported
   */
  if (h->userinfo != NULL) {
	char *basic_credentials;

	if (sent_authorization) {
	  log_msg((LOG_ERROR_LEVEL, "Multiple Authorization headers requested"));
	  return(-1);
	}

	mime_encode_base64((unsigned char *) h->userinfo, strlen(h->userinfo),
					   &basic_credentials);
	ds_asprintf(&request, "Authorization: Basic %s\n", basic_credentials);
	/* XXX the caller should zap h->userinfo */
	log_msg((LOG_DEBUG_LEVEL, "Authorization: Basic %s", basic_credentials));
	log_msg((LOG_TRACE_LEVEL, "[Sent Authorization field from context]"));
	strzap(basic_credentials);
	sent_authorization++;
  }

  /*
   * Send the userinfo field as an Authorization header, but only if not
   * already done.
   * XXX HTTP Digest is not supported
   */
  if (h->uri->userinfo != NULL) {
	char *basic_credentials;

	if (sent_authorization) {
	  log_msg((LOG_ERROR_LEVEL, "Multiple Authorization headers requested"));
	  return(-1);
	}

	mime_encode_base64((unsigned char *) h->uri->userinfo,
					   strlen(h->uri->userinfo), &basic_credentials);
	ds_asprintf(&request, "Authorization: Basic %s\n", basic_credentials);
	/* XXX the caller should zap h->uri->userinfo */
	log_msg((LOG_DEBUG_LEVEL, "Authorization: Basic %s",
			 basic_credentials));
	strzap(basic_credentials);
	log_msg((LOG_TRACE_LEVEL, "[Sent userinfo field from URI]"));
	sent_authorization++;
  }

  if (h->cookie_args != NULL) {
	if (use_authorization_header) {
	  char *c, *encoded_auth;
	  Ds ds;

	  if (sent_authorization) {
		log_msg((LOG_ERROR_LEVEL, "Multiple Authorization headers requested"));
		return(-1);
	  }

	  /* Use an RFC 2617 style header rather than cookies. */
	  ds_asprintf(&request, "Authorization: DACS ");
	  ds_init(&ds);
	  for (i = 0; i < dsvec_len(h->cookie_args); i++) {
		c = (char *) dsvec_ptr(h->cookie_args, i, char *);
		ds_asprintf(&ds, "%s%s", i > 0 ? "; " : "", c);
	  }
	  mime_encode_base64((unsigned char *) ds_buf(&ds), ds_len(&ds) - 1,
						 &encoded_auth);
	  ds_asprintf(&request, "%s\r\n", encoded_auth);
	  sent_authorization++;
	}
	else {
	  ds_asprintf(&request,
				  "Cookie: %s", dsvec_ptr(h->cookie_args, 0, char *));
	  for (i = 1; i < dsvec_len(h->cookie_args); i++)
		ds_asprintf(&request, "%s%s",
					COOKIE_SEP_STR, dsvec_ptr(h->cookie_args, i, char *));
	  ds_asprintf(&request, "\r\n");
	}
  }

  body_len = 0;
  if (h->npost_params) {
	body = ds_init(NULL);
	for (i = 0; i < h->npost_params; i++) {
	  char *n, *v;

	  n = h->post_params[i].name;
	  v = h->post_params[i].value;

	  if (!h->post_params[i].encoded) {
		v = url_encode(v, 0);
		if (n != NULL)
		  n = url_encode(n, 0);
	  }

	  if (n != NULL)
		ds_asprintf(body, "%s=%s%s", n, v,
					(i + 1) < h->npost_params ? "&" : "");
	  else
		ds_asprintf(body, "%s%s", v, (i + 1) < h->npost_params ? "&" : "");
	}
	body_len = ds_len(body) - 1;
	ds_asprintf(&request, "Content-Length: %d\r\n", body_len);

	if (h->content_type != NULL)
	  ds_asprintf(&request, "Content-Type: %s\r\n", h->content_type);
	else
	  ds_asprintf(&request,
				  "Content-Type: application/x-www-form-urlencoded\r\n");
  }
  else {
	if (h->content_type != NULL)
	  ds_asprintf(&request, "Content-Type: %s\r\n", h->content_type);

	if ((body = h->message_body) != NULL) {
	  body_len = ds_len(body);
	  ds_asprintf(&request, "Content-Length: %d\r\n", body_len);
	}
  }

  log_msg(((Log_level) (LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG),
		   "Request:\n%s", ds_buf(&request)));
  if (verbose_flag > 1)
	fprintf(stderr, "Request:\n%s", ds_buf(&request));

  rc = write_to_server(h, ds_buf(&request), ds_len(&request) - 1);
  if (rc != -1)
	http_start_data(h, body, body_len);

  return(rc);
}

static int
http_open(Http *h, char *url)
{
  int rc;

  if ((h->uri = http_parse_uri(url)) != NULL) {
	if (strcaseeq(h->uri->scheme, "https") && h->ssl_args == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "SSL is not configured for https method"));
	  rc = -1;
	}
	else {
	  rc = send_request(h);
	  /*
	   * If producing debugging or tracing output, emit a blank line
	   * to separate the request from whatever might be printed next.
	   */
	  log_msg((LOG_DEBUG_LEVEL, ""));
	}
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "Error parsing URL: \"%s\"", url));
	rc = -1;
  }

  return(rc);
}

/*
 * Wait for a response from the server, then read all response headers.
 * Return -1 if an error occurs, otherwise 0.
 * If no error occurs, the caller may use http_read_response_body() to get
 * the message body.
 */
static int
http_wait_response(Http *h)
{
  int rc, sock;

  sock = (h->ssl_args != NULL) ? h->ssl_read_fd : h->sd;

  /*
   * No more writing to the connection/pipe - close it now.
   */
  log_msg((LOG_TRACE_LEVEL, "Closing output stream..."));
  if (h->ssl_args != NULL) {
	close(h->ssl_write_fd);
	h->ssl_write_fd = -1;
	log_msg((LOG_TRACE_LEVEL, "SSL pipe closed"));
  }
  else {
	/*
	 * 23-Sep-08 doing a shutdown sometimes causes a subsequent read on the
	 * socket in parse_response() to return EOF... (why?)
	 * But if the shutdown is not done, some servers wait for more input
	 * (maybe timing out eventually).
	 * For now, we'll call shutdown() only if it seems the server is waiting...
	 */
	struct timeval timeout;

	timeout.tv_sec = 4;
	timeout.tv_usec = 0;
	if (net_input_or_timeout(h->sd, &timeout) != 1)
	  shutdown(h->sd, SHUT_WR);
  }

  h->in_data = 0;

  log_msg((LOG_TRACE_LEVEL, "Reading response..."));

  rc = parse_response(h);

  return(rc);
}

/*
 * This is a lower-level interface for issuing an HTTP request.
 */
int
http_request(Http *h, char *url)
{

  http_set_user_agent_string(NULL);

  if (http_open(h, url) == -1)
	return(-1);

  if (http_wait_response(h) == -1)
	return(-1);

  return(0);
}

/*
 * Read some or all of the entity body.
 * Return -1 if an error occurs, 0 if EOF is reached, 1 otherwise.
 */
int
http_read_response_body(Http *h, Ds *ds)
{
  int st;

  if (h->fp == NULL) {
	int sock;

	if (h->ssl_args != NULL)
	  sock = h->ssl_read_fd;
	else
	  sock = h->sd;
	if ((h->fp = fdopen(sock, "r")) == NULL) {
	  log_err((LOG_ERROR_LEVEL, "http_read_response_body: fdopen"));
	  return(-1);
	}
  }

  /*
   * Read and append the next chunk of the response.
   * XXX this should support chunked transfer coding (3.6)
   */
  dsio_set(ds, h->fp, NULL, 4096, 1);
  st = dsio_load(ds);

  return(st);
}

FILE *
http_read_response_stream(Http *h)
{

  if (h->fp == NULL) {
	int sock;

	if (h->ssl_args != NULL)
	  sock = h->ssl_read_fd;
	else
	  sock = h->sd;

	if ((h->fp = fdopen(sock, "r")) == NULL) {
	  log_err((LOG_ERROR_LEVEL, "http_read_response_stream: fdopen"));
	  return(NULL);
	}
  }

  return(h->fp);
}

void
http_close(Http *h)
{

  if (h->ssl_args != NULL) {
	if (h->ssl_read_fd != -1)
	  close(h->ssl_read_fd);
	if (h->ssl_write_fd != -1)
	  close(h->ssl_write_fd);
	if (h->ssl_error_fd != -1)
	  close(h->ssl_error_fd);
	h->ssl_read_fd = h->ssl_write_fd = h->ssl_error_fd = -1;
  }
  else if (h->sd != -1) {
	close(h->sd);
	h->sd = -1;
  }

  if (h->fp != NULL) {
	fclose(h->fp);
	h->fp = NULL;
  }
}

Http *
http_init(Http_version *version)
{
  Http *h;

  h = ALLOC(Http);
  h->sd = -1;
  if ((h->version = version) == NULL) {
	h->version = ALLOC(Http_version);
	h->version->major = HTTP_VERSION_MAJOR;
	h->version->minor = HTTP_VERSION_MINOR;
  }
	
  h->fp = NULL;
  h->message_headers = NULL;
  h->message_body = NULL;
  h->content_type = NULL;
  h->ssl_args = NULL;
  h->ssl_read_fd = -1;
  h->ssl_write_fd = -1;
  h->ssl_error_fd = -1;
  h->ssl_env = NULL;
  h->post_params = NULL;
  h->npost_params = 0;
  h->cookie_args = NULL;
  h->in_data = 0;
  h->uri = NULL;
  h->userinfo = NULL;
  h->proxy_userinfo = NULL;
  h->method = HTTP_GET_METHOD;
  h->server_options = 0;
  h->response_headers = NULL;
  h->response_kwv = NULL;
  h->status_line = NULL;
  h->status_code = 0;

  h->proxy.hostname = NULL;
  h->proxy.portstr = NULL;
  h->proxy.portnum = 0;

  return(h);
}

/*
 * The request has been almost fully configured in H and URL contains any
 * query component.  Do any required SSL configuration and start the request.
 * Return -1 if an error occurs, 0 otherwise.
 */
int
http_invoke_request(Http *h, char *url, Http_connection_mode mode,
					char **errmsg)
{
  int rc;

  /* XXX assume an https url is still ok to use as-is if HTTP_SSL_OFF */
  if (mode != HTTP_SSL_OFF
	  && (mode != HTTP_SSL_URL_SCHEME || strneq(url, "https:", 6))) {
	int argc, i;
	char **argv, *p, *remote;
	Uri *puri;
	Dsvec *v;

	if ((puri = http_parse_uri(url)) == NULL) {
	  *errmsg = "URL parse failed";
	  return(-1);
	}

	if (puri->port_given == NULL)
	  puri->port = DACS_DEFAULT_SSL_PORT;

	remote = ds_xprintf("%s:%u", puri->host, puri->port);

	v = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(v, conf_val(CONF_SSL_PROG));

#ifndef USE_STUNNEL
	/*
	 * This expects DACS's sslclient(1).
	 * If this is being run via the http(1) command, which does not use
	 * DACS config files, sslclient should not process config files either.
	 * These DACS flags must precede the program's own flags.
	 */
	if (is_http_prog)
	  dsvec_add_ptr(v, (void *) "-un");
	else {
	  if (dacs_conf == NULL || dacs_conf->dacs_conf == NULL
		  || dacs_conf->site_conf == NULL)
		dsvec_add_ptr(v, (void *) "-un");
	  else {
		dsvec_add_ptr(v, (void *) "-c");
		dsvec_add_ptr(v, (void *) dacs_conf->dacs_conf);
		dsvec_add_ptr(v, (void *) "-sc");
		dsvec_add_ptr(v, (void *) dacs_conf->site_conf);
		dsvec_add_ptr(v, (void *) "-uj");
		dsvec_add_ptr(v, (void *) conf_val(CONF_JURISDICTION_NAME));
	  }
	}
#endif

	argc = 0;
	if ((p = conf_val(CONF_SSL_PROG_ARGS)) != NULL) {
	  static Mkargv conf = { 0, 0, " ", NULL, NULL };

	  if ((argc = mkargv(p, &conf, &argv)) == -1) {
		*errmsg = "Invalid SSL_PROG_ARGS directive";
		return(-1);
	  }
	}

#ifdef USE_STUNNEL
	/*
	 * XXX The following method is a crock but I haven't found a better
	 * method than using stunnel 3.26 so this kludge will remain for
	 * the time being.
	 */
	dsvec_add_ptr(v, "-r");
	dsvec_add_ptr(v, remote);
	if ((p = conf_val(CONF_SSL_PROG_CLIENT_CRT)) != NULL) {
	  dsvec_add_ptr(v, "-p");
	  dsvec_add_ptr(v, p);
	}
	dsvec_add_ptr(v, "-c");
	if (mode == HTTP_SSL_ON_VERIFY) {
	  dsvec_add_ptr(v, "-v");
	  dsvec_add_ptr(v, "1");
	  dsvec_add_ptr(v, "-A");
	  dsvec_add_ptr(v, conf_val(CONF_SSL_PROG_CA_CRT));
	}
	for (i = 0; i < argc; i++)
	  dsvec_add_ptr(v, argv[i]);
#else
	/*
	 * This expects DACS's sslclient(1).
	 */
	if ((p = conf_val(CONF_SSL_PROG_CLIENT_CRT)) != NULL) {
	  dsvec_add_ptr(v, (void *) "-ccf");
	  dsvec_add_ptr(v, (void *) p);
	}

	if (mode == HTTP_SSL_ON_VERIFY) {
	  dsvec_add_ptr(v, (void *) "-vt");
	  dsvec_add_ptr(v, (void *) "peer");
	  dsvec_add_ptr(v, (void *) "-caf");
	  dsvec_add_ptr(v, conf_val(CONF_SSL_PROG_CA_CRT));
	}

	for (i = 0; i < argc; i++)
	  dsvec_add_ptr(v, argv[i]);

	dsvec_add_ptr(v, remote);
#endif

	dsvec_add_ptr(v, NULL);
	http_set_ssl_params(h, v);
  }

  if ((rc = http_request(h, url)) == -1) {
	*errmsg = ds_xprintf("Error processing HTTP request: \"%s\"", url);
	return(-1);
  }

  http_read_response_stream(h);

  return(0);
}

/*
 * URL must not contain a query component - it will be added if necessary.
 */
Http *
http_invoke_stream(char *url, Http_method method, Http_connection_mode mode,
				   int nparams, Http_params *params, Ds *body,
				   char **cookies, FILE **fp, int *status_code,
				   Dsvec *response_headers, char **errmsg)
{
  int i, st;
  Http *h;

  h = http_init(NULL);
  h->method = method;
  h->response_headers = response_headers;
  h->message_body = body;

  http_set_cookies(h, cookies);

  if (method == HTTP_POST_METHOD
	  || method == HTTP_PUT_METHOD
	  || method == HTTP_OPTIONS_METHOD)
	http_set_post_params(h, nparams, params);
  else {
	Ds ds;

	/* GET, HEAD, etc. */
	ds_init(&ds);
	ds_set(&ds, url);
	for (i = 0; i < nparams; i++) {
	  ds_asprintf(&ds, "%s%s=%s", i == 0 ? "?" : "&",
				  params[i].name, url_encode(params[i].value, 0));
	}
	url = ds_buf(&ds);
  }

  if ((st = http_invoke_request(h, url, mode, errmsg)) == -1)
	return(NULL);

  if (fp != NULL)
	*fp = h->fp;

  if (status_code != NULL)
	*status_code = h->status_code;

  return(h);
}

/*
 * If the input-output parameter REPLY_LEN is greater than zero, it is the
 * maximum acceptable reply length, otherwise there is no a priori limit.
 * If there is a response, it will be null-terminated but the returned length
 * will not count this byte.
 * Return -1 if an error occurs, 0 otherwise.
 */
int
http_get_response_body(Http *h, char **reply, int *reply_len)
{
  int rc;
  Ds response;

  ds_init(&response);
  if (*reply_len > 0)
	response.len_limit = *reply_len;

  while ((rc = http_read_response_body(h, &response)) > 0)
	;

  if (rc == -1) {
	*reply = "Error reading HTTP response";
	return(-1);
  }

  if (ds_len(&response) == 0) {
	*reply = "";
	*reply_len = 0;
  }
  else {
	*reply_len = ds_len(&response);
	ds_appendc(&response, (int) '\0');
	*reply = ds_buf(&response);
  }

  return(0);
}

/*
 * This is the higher-level interface for issuing an HTTP request.
 *
 * Invoke URL using method METHOD, passing it the NPARAMS parameters
 * in PARAMS and COOKIES.
 *
 * REPLY is set to point to the reply if the invocation is successful,
 * otherwise it points to an error message.  In either case the reply buffer
 * is obtained via malloc(3) and is always null terminated.
 *
 * NOTE:
 * REPLY_LEN is an input-output parameter; on input, it is the maximum length
 * of the reply to return (it doesn't apply to error messages, however); if
 * it is negative, the length of the reply will only be limited by available
 * resources.
 * As an output, REPLY_LEN is set to the actual length of the reply (again,
 * except for error messages), excluding the null terminator.
 * If successful, 0 is returned; -1 is returned if an error occurs.
 */
int
http_invoke(char *url, Http_method method, Http_connection_mode mode,
			int nparams, Http_params *params, Ds *body, char **cookies,
			char **reply, int *reply_len, int *status_code,
			Dsvec *response_headers)
{
  int rc;
  char *errmsg;
  Http *h;

  errmsg = NULL;
  h = http_invoke_stream(url, method, mode, nparams, params, body,
						 cookies, NULL, status_code, response_headers,
						 &errmsg);
  if (h == NULL) {
	*reply = errmsg;
	return(-1);
  }

  if (*reply_len == 0) {
	http_close(h);
	*reply = strdup("");
	return(0);
  }

  if ((rc = http_get_response_body(h, reply, reply_len)) == -1) {
	/* Handle is not closed. (?) */
	return(-1);
  }

  http_close(h);

  return(0);
}

char *
uri_sa(Ds *ods, Uri *uri)
{
  Ds *ds, xds;

  if (ods == NULL) {
    ds_init(&xds);
    ds = &xds;
  }
  else
    ds = ods;

  ds_asprintf(ds, "%s://", uri->scheme);
  ds_asprintf(ds, "%s", uri->server);
  if (uri->path != NULL)
	ds_asprintf(ds, "%s", uri->path);

  return(ds_buf(ds));
}

/*
 * A simpler interface to http_invoke() for the GET method.
 * URL may have query arguments but no fragment component.
 * MORE_ARGS is a vector of pointers to zero or more Uri_query_arg structures.
 */
int
http_get(char *url, Dsvec *more_args, char **cookies, char **reply,
		 int *reply_len, int *status_code, Dsvec *response_headers)
{
  int i, st;
  char *u;
  Dsvec *params;
  Uri *uri;

  if ((uri = http_parse_uri(url)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Invalid URL: %s", url));
	return(-1);
  }

  params = dsvec_init(NULL, sizeof(Http_params));
  for (i = 0; i < dsvec_len(uri->query_args); i++) {
	Uri_query_arg *arg;

	arg = (Uri_query_arg *) dsvec_ptr_index(uri->query_args, i);
	http_param(params, arg->name, arg->value, NULL, 0);
  }
  
  if (more_args != NULL) {
	Uri_query_arg *arg;

	for (i = 0; i < dsvec_len(more_args); i++) {
	  arg = (Uri_query_arg *) dsvec_ptr_index(more_args, i);
	  http_param(params, arg->name, arg->value, NULL, 0);
	}
  }

  u = uri_sa(NULL, uri);
  st = http_invoke(u, HTTP_GET_METHOD, HTTP_SSL_URL_SCHEME,
				   dsvec_len(params), (Http_params *) dsvec_base(params),
				   NULL, cookies, reply, reply_len,
				   status_code, response_headers);

  return(st);
}

/*
 * A simpler interface to http_invoke() for the POST method.
 * URL may have query arguments.
 * POST_ARGS is a vector of pointers to zero or more Uri_query_arg structures.
 */
int
http_post(char *url, Dsvec *post_args, char **cookies, char **reply,
		  int *reply_len, int *status_code, Dsvec *response_headers)
{
  int i, st;
  Dsvec *params;
  Uri *uri;

  if (post_args != NULL) {
	Uri_query_arg *arg;

	params = dsvec_init(NULL, sizeof(Http_params));
	for (i = 0; i < dsvec_len(post_args); i++) {
	  arg = (Uri_query_arg *) dsvec_ptr_index(post_args, i);
	  http_param(params, arg->name, arg->value, NULL, 0);
	}
  }
  else
	params = NULL;

  st = http_invoke(url, HTTP_POST_METHOD, HTTP_SSL_URL_SCHEME,
				   dsvec_len(params), (Http_params *) dsvec_base(params),
				   NULL, cookies, reply, reply_len,
				   status_code, response_headers);

  return(st);
}

static int
insert_file(Http_params *param)
{
  char *buf;

  if (streq(param->filename, "-")) {
	Ds *ds;

	if (feof(stdin)) {
	  fprintf(stderr, "Can't read stdin more than once.\n");
	  return(-1);
	}
	if ((ds = ds_getf(stdin)) == NULL) {
	  fprintf(stderr, "Could not read stdin.\n");
	  return(-1);
	}
	buf = ds_buf(ds);
  }
  else {
	if (load_file(param->filename, &buf, NULL) == -1) {
	  fprintf(stderr, "Could not read file \"%s\".\n", param->filename);
	  return(-1);
	}
  }

  if (param->encoded)
	param->value = buf;
  else
	param->value = url_encode(buf, 0);

  return(0);
}

static int
set_cookies_from_file(Http *h, char *filename)
{
  char *p, *buf, *s;
  Dsvec *v;

  if (load_file(filename, &buf, NULL) == -1)
	return(-1);

  v = dsvec_init(NULL, sizeof(char *));
  s = buf;
  while ((p = strsep(&s, "\n")) != NULL) {
	if (*p != '\0')
	  dsvec_add_ptr(v, p);
  }
  dsvec_add_ptr(v, NULL);
  http_set_cookies(h, (char **) dsvec_base(v));

  return(0);
}

static void
usage(void)
{

  fprintf(stderr, "Usage: dacshttp [flags ...] uri\n");
  fprintf(stderr, "Flags:\n");
  fprintf(stderr, "  -delete            :\n");
  fprintf(stderr, "  -get               :\n");
  fprintf(stderr, "  -head              :\n");
  fprintf(stderr, "  -post              :\n");
  fprintf(stderr, "  -options           :\n");
  fprintf(stderr, "  -soptions          :\n");
  fprintf(stderr, "  -put               : ");
  fprintf(stderr, "Use GET (default)/POST/HEAD/PUT/DELETE/OPTIONS method\n");
  fprintf(stderr, "  -v                 : ");
  fprintf(stderr, "Increase verbosity level\n");
  fprintf(stderr, "  -prompt            : ");
  fprintf(stderr, "Prompt user before taking certain actions\n");
  fprintf(stderr, "  -ih                : ");
  fprintf(stderr, "Include HTTP response headers in output\n");
  fprintf(stderr, "  -ll loglevel       : ");
  fprintf(stderr, "Set the logging level to loglevel\n");
  fprintf(stderr, "  --version          : ");
  fprintf(stderr, "Print version info, then exit immediately\n");
  fprintf(stderr, "  -ct string         :\n");
  fprintf(stderr, "  --content-type str : ");
  fprintf(stderr, "\n");
  fprintf(stderr,
		  "Set the Content-Type request-header to the given string\n");
  fprintf(stderr, "  -header name value : ");
  fprintf(stderr, "Emit HTTP header \"name: value\"\n");
  fprintf(stderr, "  -headers filename  : ");
  fprintf(stderr, "Read additional message headers from filename\n");
  fprintf(stderr, "  -body filename     : ");
  fprintf(stderr,
		  "Read the message body from filename (\"-\" means stdin)\n");
  fprintf(stderr, "  -user-agent name   : ");
  fprintf(stderr, "Set the User-Agent request-header to the given string\n");
  fprintf(stderr, "  -p name value      : ");
  fprintf(stderr, "Pass 'name=value'   (changes default to POST method)\n");
  fprintf(stderr, "  -f name filename   : ");
  fprintf(stderr, "Pass the contents of filename as 'name=contents' in\n");
  fprintf(stderr, "                     (changes default to POST method\n");
  fprintf(stderr, "  -proto version     : ");
  fprintf(stderr, "Use the specified version of the HTTP protocol\n");
  fprintf(stderr, "  -proxy hostname:port  : ");
  fprintf(stderr, "Use the specified proxy server\n");
  fprintf(stderr, "  -cookies filename  : ");
  fprintf(stderr, "Obtain cookies to send from filename, one per line\n");
  fprintf(stderr, "  -ah                : ");
  fprintf(stderr, "Send any cookies using the Authorization header\n");
  fprintf(stderr, "  -u | --user <username>:<password>: ");
  fprintf(stderr, "Send an HTTP Basic auth Authorization header\n");
  fprintf(stderr, "  -U | --proxyuser <username>:<password>: ");
  fprintf(stderr, "Send an HTTP Basic auth Proxy-Authorization header\n");
  fprintf(stderr, "  -ssl command-line  : ");
  fprintf(stderr, "Connect using SSL via another program, e.g.,\n");
  fprintf(stderr, "-ssl \"/usr/local/sbin/stunnel -r vs:443 -c");
  fprintf(stderr, " -v 1 -A /usr/local/apache/conf/ssl.crt/dacs-ca.crt");
  fprintf(stderr, " -p client_cert.pem\"\n");
  fprintf(stderr, "  -ssl-flags flags    : append flags to SSL command\n");
  fprintf(stderr, "\n");

  exit(1);
}

int
dacshttp_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, have_method, prompt_user, nredirects, rc, saw_ssl_flag;
  int include_headers;
  char *proto, *redirect_uri, *request_uri;
  Dsvec *ssl_args, *ssl_flags, *post_params;
  Ds response;
  FILE *vfp;
  Http *h;
  Http_params *params;
  Http_version *http_version;
  Log_desc *ld;
  Uri *uri;

  set_dacs_app_name(argv[0]);

  /*
   * Don't leave a core dump behind that might reveal passwords or cookies.
   */
  dacs_disable_dump();

  ld = log_init(NULL, 0, NULL, "http", LOG_NONE_LEVEL, NULL);
  log_set_level(ld, LOG_WARN_LEVEL);
  log_set_desc(ld, LOG_ENABLED);
  log_set_flags(ld, LOG_MATCH_NORMAL | LOG_MATCH_SENSITIVE);
  verbose_flag = 0;
  vfp = stderr;

  if (argc < 2) {
  fail:
	usage();
	/*NOTREACHED*/
  }
 
  ssl_args = ssl_flags = NULL;
  saw_ssl_flag = 0;
  have_method = 0;
  prompt_user = 0;
  include_headers = 0;
  nredirects = 0;
  proto = NULL;
  http_set_user_agent_string(NULL);

  post_params = dsvec_init(NULL, sizeof(Http_params));
  h = http_init(NULL);

  for (i = 1; i < argc; i++) {
	if (argv[i][0] != '-')
	  break;
	/* The following are standalone flags. */
	if (strcaseeq(argv[i], "--version")) {
	  dacs_version(stderr);
	  return(0);
	}
	if (strcaseeq(argv[i], "-ah")) {
	  use_authorization_header = 1;
	  continue;
	}
	if (strcaseeq(argv[i], "-post")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_POST_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-get")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_GET_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-head")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_HEAD_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-put")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_PUT_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-delete")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_DELETE_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-options")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_OPTIONS_METHOD;
	  continue;
	}
	if (strcaseeq(argv[i], "-soptions")) {
	  if (have_method++)
		goto fail;
	  h->method = HTTP_OPTIONS_METHOD;
	  h->server_options = 1;
	  continue;
	}

	if (strcaseeq(argv[i], "-prompt")) {
	  prompt_user = 1;
	  continue;
	}

	if (strcaseeq(argv[i], "-proto")) {
	  if (argv[i + 1] == NULL || proto != NULL)
		goto fail;
	  proto = argv[++i];
	  if ((http_version = http_parse_version(proto)) == NULL) {
		fprintf(stderr, "Invalid HTTP version: \"%s\"\n", proto);
		goto fail;
	  }
	  if (http_version->major > HTTP_VERSION_MAJOR
		  || http_version->minor > HTTP_VERSION_MINOR) {
		fprintf(stderr, "Unimplemented HTTP version.\n");
		goto fail;
	  }
	  continue;
	}

	if (strcaseeq(argv[i], "-ih")) {
	  include_headers = 1;
	  vfp = stdout;
	  continue;
	}

	if (streq(argv[i], "-v")) {
	  verbose_flag++;
	  continue;
	}

	/* The following take at least one additional argument. */
	if (argv[i + 1] == NULL)
	  goto fail;

	if (streq(argv[i], "-ll")) {
	  Log_level ll;

	  i++;
	  if ((ll = log_lookup_level(argv[i])) == LOG_INVALID_LEVEL
		  || log_set_level(ld, ll) == LOG_INVALID_LEVEL) {
		fprintf(stderr, "Invalid log_level: %s", argv[i]);
		goto fail;
	  }
	  continue;
	}

	if (strcaseeq(argv[i], "-ssl")) {
	  char *p, *s;

	  if (argv[i + 1] == NULL || ssl_args != NULL)
		goto fail;

	  /*
	   * e.g.:
	   * /k/bin/stunnel -r vs:443 -c -v 1 \
	   *     -A /usr/local/apache/conf/ssl.crt/dacs-ca.crt
	   */
	  s = argv[i + 1];
	  ssl_args = dsvec_init(NULL, sizeof(char *));
	  while ((p = strsep(&s, " ")) != NULL) {
		if (*p != '\0')
		  dsvec_add_ptr(ssl_args, p);
	  }
	  saw_ssl_flag = 1;

	  i++;
	  continue;
	}

	if (strcaseeq(argv[i], "-ssl-flags")) {
	  char *p, *s;

	  if (argv[i + 1] == NULL)
		goto fail;

	  if (ssl_flags == NULL)
		ssl_flags = dsvec_init(NULL, sizeof(char *));

	  s = argv[i + 1];
	  while ((p = strsep(&s, " ")) != NULL) {
		if (*p != '\0')
		  dsvec_add_ptr(ssl_flags, p);
	  }

	  i++;
	  continue;
	}

	if (streq(argv[i], "-u") || streq(argv[i], "--user")) {
	  if (argv[++i] == NULL)
		goto fail;
	  if (h->userinfo != NULL)
		goto fail;
	  h->userinfo = strdup(argv[i]);
	  continue;
	}

	if (streq(argv[i], "-U") || streq(argv[i], "--proxyuser")) {
	  if (argv[++i] == NULL)
		goto fail;
	  if (h->proxy_userinfo != NULL)
		goto fail;
	  h->proxy_userinfo = strdup(argv[i]);
	  continue;
	}

	if (strcaseeq(argv[i], "-user-agent")) {
	  if (argv[++i] == NULL)
		goto fail;
	  http_set_user_agent_string(argv[i]);
	  continue;
	}

	if (strcaseeq(argv[i], "-proxy")) {
	  if (argv[++i] == NULL)
		goto fail;
	  if (net_parse_hostname_port(argv[i], &h->proxy.hostname,
								  &h->proxy.portstr,
								  &h->proxy.portnum) == -1
		  || h->proxy.portstr == NULL) {
		fprintf(stderr, "Invalid proxy hostname or port/service: \"%s\"\n",
				argv[i]);
		goto fail;
	  }
	  continue;
	}

	if (strcaseeq(argv[i], "-ct") || strcaseeq(argv[i], "--content-type")) {
	  if (argv[++i] == NULL || h->content_type != NULL)
		goto fail;
	  h->content_type = argv[i];
	  continue;
	}

	if (strcaseeq(argv[i], "-header")) {
	  char *hname, *hvalue;

	  if (argv[++i] == NULL)
		goto fail;
	  hname = argv[i];
	  if (argv[++i] == NULL)
		goto fail;
	  hvalue = argv[i];

	  if (h->message_headers == NULL)
		h->message_headers = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(h->message_headers, ds_xprintf("%s: %s", hname, hvalue));
	  continue;
	}

	if (strcaseeq(argv[i], "-headers")) {
	  Ds ds;
	  FILE *fp;

	  if (argv[++i] == NULL)
		goto fail;
	  if ((fp = fopen(argv[i], "r")) == NULL) {
		fprintf(stderr, "Can't read headers from \"%s\".\n", argv[i]);
		goto fail;
	  }

	  if (h->message_headers == NULL)
		h->message_headers = dsvec_init(NULL, sizeof(char *));
	  ds_init(&ds);
	  ds.delnl_flag = 1;
	  ds.crnl_flag = 1;
	  dsio_set(&ds, fp, NULL, 0, 0);
	  dsvec_load(&ds, h->message_headers);

	  if (ferror(fp)) {
		fclose(fp);
		log_msg((LOG_ERROR_LEVEL, "Error reading message headers"));
		goto fail;
	  }

	  fclose(fp);
	  continue;
	}

	if (strcaseeq(argv[i], "-body")) {
	  FILE *fp;

	  if (argv[++i] == NULL || h->message_body != NULL)
		goto fail;
	  if (streq(argv[i], "-"))
		fp = stdin;
	  else {
		if ((fp = fopen(argv[i], "r")) == NULL) {
		  fprintf(stderr, "Can't read body from \"%s\".\n", argv[i]);
		  goto fail;
		}
	  }
	  h->message_body = ds_init(NULL);
	  dsio_set(h->message_body, fp, NULL, 0, 0);
	  if (dsio_load(h->message_body) == -1) {
		fclose(fp);
		fprintf(stderr, "Error reading body from \"%s\".\n", argv[i]);
		goto fail;
	  }
	  fclose(fp);

	  continue;
	}

	if (strcaseeq(argv[i], "-cookies")) {
	  if (set_cookies_from_file(h, argv[i + 1]) == -1) {
		fprintf(stderr, "Couldn't get cookies from \"%s\".\n", argv[i + 1]);
		return(-1);
	  }
	  i++;
	  continue;
	}

	if (argv[i + 2] == NULL)
	  goto fail;

	switch (argv[i][1]) {
	case 'p':
	  /* Encode the name and value. */
	  params = http_param(post_params, NULL,
						  ds_xprintf("%s=%s", url_encode(argv[i + 1], 0),
									 url_encode(argv[i + 2], 0)), NULL, 1);
	  i += 2;
	  if (!have_method)
		h->method = HTTP_POST_METHOD;
	  break;

	case 'f':
	  /* Encode the name now and the value later. */
	  params = http_param(post_params,
						  ds_xprintf("%s", url_encode(argv[i + 1], 0)),
						  NULL, argv[i + 2], 0);
	  if (insert_file(params) == -1)
		return(-1);
	  i += 2;
	  if (!have_method)
		h->method = HTTP_POST_METHOD;
	  break;

	default:
	  goto fail;
	  /*NOTREACHED*/
	}
  }

  if ((request_uri = argv[i]) == NULL)
	goto fail;

  if (argv[i + 1] != NULL)
	goto fail;

  if (verbose_flag == 1)
	log_set_level(ld, LOG_DEBUG_LEVEL);
  else if (verbose_flag > 1)
	log_set_level(ld, LOG_TRACE_LEVEL);

 http_invoke:

  if ((uri = http_parse_uri(request_uri)) == NULL) {
	fprintf(stderr, "Parse of URL '%s' failed.\n", request_uri);
	return(-1);
  }

  /*
   * If the scheme is HTTPS but no SSL configuration argument was given,
   * try to do something sensible.
   */
  if ((ssl_args == NULL || dsvec_len(ssl_args) == 1)
	  && strcaseeq(uri->scheme, "https")) {
	char *prog;

	if (ssl_args == NULL) {
	  ssl_args = dsvec_init(NULL, sizeof(char *));
	  prog = DACS_HOME/**/"/bin/sslclient";
	  dsvec_add_ptr(ssl_args, prog);
	}

	if (ssl_flags != NULL) {
	  int j;
	  char *f;

	  for (j = 0; j < dsvec_len(ssl_flags); j++) {
		f = dsvec_ptr_index(ssl_flags, j);
		dsvec_add_ptr(ssl_args, f);
	  }
	}

	if (uri->port_given != NULL)
	  dsvec_add_ptr(ssl_args, ds_xprintf("%s:%u", uri->host, uri->port));
	else
	  dsvec_add_ptr(ssl_args, uri->host);

	if (verbose_flag)
	  fprintf(stderr, "Using SSL: %s\n", strjoin(ssl_args, " "));
  }

  /* If there is SSL config, set it up. */
  if (ssl_args != NULL) {
	dsvec_add_ptr(ssl_args, NULL);
	http_set_ssl_params(h, ssl_args);
  }

  if (verbose_flag) {
	Uri_query_arg *r;

	fprintf(stderr, "Method: %s\n", http_method_to_string(h->method));
	fprintf(stderr, "Scheme: %s\n", uri->scheme);
	fprintf(stderr, "Host: %s\n", uri->host);
	fprintf(stderr, "Port: %u\n", uri->port);
	fprintf(stderr, "URL: %s\n", uri->uri);
	if (h->content_type != NULL)
	  fprintf(stderr, "Content-Type: %s\n", h->content_type);
	if (uri->path != NULL)
	  fprintf(stderr, "Path: %s\n", uri->path);
	for (i = 0; i < dsvec_len(uri->query_args); i++) {
	  r = dsvec_ptr_index(uri->query_args, i);
	  fprintf(stderr, "Query Arg: %s=\"%s\"\n", r->name, r->value);
	}
	fprintf(stderr, "User-Agent: %s\n", user_agent_string);
  }

  http_set_post_params(h, dsvec_len(post_params),
					   (Http_params *) dsvec_base(post_params));

  h->response_headers = dsvec_init(NULL, sizeof(char *));
  h->response_kwv = kwv_init(16);
  h->response_kwv->icase = 1;
  h->response_kwv->dup_mode = KWV_NO_DUPS;

  if (http_request(h, request_uri) == -1) {
	fprintf(stderr, "Can't get \"%s\"\n", request_uri);
	http_close(h);
	return(-1);
  }

  if (verbose_flag || include_headers)
	fprintf(vfp, "Status-Line: %s\n", h->status_line);

  if ((verbose_flag  || include_headers) && h->response_headers != NULL) {
    char *p;
  
    for (i = 0; (p = dsvec_ptr(h->response_headers, i, char *)) != NULL; i++)
	  fprintf(vfp, "%s\n", p);
  } 
  fflush(vfp);

  /* RFC 2616 S14.30 */
  redirect_uri = kwv_lookup_value(h->response_kwv, "Location");

  if (redirect_uri == NULL || prompt_user || h->method != HTTP_HEAD_METHOD) {
	FILE *ofp;

	/* Read and display the response. */
	if (verbose_flag || include_headers) {
	  fprintf(vfp, "\n");
	  fflush(vfp);
	}

	if (h->status_code >= 400)
	  ofp = stderr;
	else
	  ofp = stdout;

	ds_init(&response);
	while ((rc = http_read_response_body(h, &response)) > 0) {
	  fwrite(ds_buf(&response), 1, ds_len(&response), ofp);
	  ds_reset(&response);
	}
	if (h->status_code >= 400)
	  fprintf(ofp, " (Status-Code %d)\n", h->status_code);
	fflush(ofp);

	if (rc == -1) {
	  fprintf(stderr, "Error reading response\n");
	  return(-1);
	}
  }

  if (redirect_uri != NULL && h->status_code != 201) {
	char *p;
	Ds ds;
	Uri *ruri;

	if (verbose_flag)
	  fprintf(stderr, "Received redirect to: %s\n", redirect_uri);

	/* RFC 2616 S10.3 */
	if (h->status_code == 303) {
	  h->method = HTTP_GET_METHOD;
	}
	else if (h->status_code == 301 || h->status_code == 302
			 || h->status_code == 305 || h->status_code == 307) {
	  /* Redirection will use the previous request's method. */
	}
	else {
	  fprintf(stderr, "Redirection via status code %d is not implemented\n",
			  h->status_code);
	  goto done;
	}

	/*
	 * RFC 2616 S14.30 says that this must be an absolute URI, but in
	 * practice it may not be and the expected behaviour for browsers
	 * seems to be to convert it into an absolute URI based on the URI of
	 * the redirected request.
	 */
	if ((ruri = uri_parse(redirect_uri)) == NULL) {
	  if (*redirect_uri == '/')
		request_uri = ds_xprintf("%s%s",
								 uri_scheme_authority(h->uri), redirect_uri);
	  else {
		char *path;

		if ((path = h->uri->path) == NULL)
		  path = "/";
		request_uri = ds_xprintf("%s%s/%s",
								 uri_scheme_authority(h->uri),
								 strdirname(strdup(path)),
								 redirect_uri);
		/* Make sure we formed a reasonable looking URI... */
		if (uri_parse(request_uri) == NULL) {
		  fprintf(stderr, "Invalid redirect URI: %s\n", request_uri);
		  goto done;
		}
	  }
	  if (verbose_flag)
		fprintf(stderr, "Will redirect to: %s\n", request_uri);
	}
	else
	  request_uri = redirect_uri;

	/*
	 * Prompt the user before following the redirect, if so configured,
	 * or if the method is not GET or HEAD (RFC 2616 S10.3).
	 * If not prompting, watch for too many redirects, including infinite
	 * loops.
	 */
	if (prompt_user
		|| (h->method != HTTP_GET_METHOD && h->method != HTTP_HEAD_METHOD)) {
	  if (h->method != HTTP_GET_METHOD && h->method != HTTP_HEAD_METHOD)
		fprintf(stderr, "Note: redirection will use %s method\n",
				http_method_to_string(h->method));

	  ds_init(&ds);
	  if ((p = ds_prompt(&ds, "Follow redirect[n]? ", DS_PROMPT_ECHO)) == NULL
		  || (!strcaseeq(p, "y") && !strcaseeq(p, "yes")))
		goto done;
	}
	else {
	  if (nredirects++ >= HTTP_MAX_REDIRECTS) {
		fprintf(stderr, "Maximum number of redirects (%d) has been exceeded.\n",
				HTTP_MAX_REDIRECTS);
		goto done;
	  }
	}

	http_close(h);
	goto http_invoke;
  }

  if (h->status_code == 407) {
	/* Server requires proxy authentication. */
	fprintf(stderr, "Server requires proxy authentication\n");
  }

 done:

  http_close(h);

  return(0);
}

#else

int
main(int argc, char **argv)
{
  int rc;
  extern int is_http_prog;
 
  is_http_prog = 1;
  if ((rc = dacshttp_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
