/*
 * 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.
 ***************************************************************************/

/*
 * CGI argument parsing
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: cgiparse.c 2833 2015-08-19 19:13:00Z brachman $";
#endif

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

#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#endif

static char *log_module_name = "cgiparse";

static char *fmakeword(CGI_input *in, char stop);
static char *makeword(char *line, char stop, int *stopseen);
static void plustospace(char *str);

static FILE *debug_fp = NULL;
static int debug_urlencode = 0;
static int test_mime = 0;

static Entity_body entity_body = {
  NULL, NULL, 0, 0, NULL
};

Entity_body *
cgiparse_get_entity_body(void)
{

  if (entity_body.body == NULL)
	return(NULL);

  return(&entity_body);
}

/*
 * Return 1 and set CLEN if Content-Length is available,
 * return 0 if Content-Length is unavailable, and return -1 if an error occurs.
 */
int
cgiparse_get_content_length(unsigned long *clen)
{
  char *content_length;

  if ((content_length = getenv("CONTENT_LENGTH")) != NULL) {
	log_msg((LOG_DEBUG_LEVEL, "CONTENT_LENGTH=%s", content_length));
	if (strnum(content_length, STRNUM_UL, clen) == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Improper CONTENT_LENGTH: %s", content_length));
	  return(-1);
	}
	return(1);
  }

  return(0);
}

CGI_input *
cgiparse_set_input(Http_method method, FILE *fp, char *buf,
				   unsigned long len, int have_length, CGI_options *options)
{
  CGI_input *in;

  if (fp == NULL && (!have_length || buf == NULL))
	return(NULL);

  in = ALLOC(CGI_input);
  in->method = method;
  in->fp = fp;
  in->buf = in->ptr = buf;
  in->length = in->rem = len;
  in->have_length = have_length;
  in->got_peeked_char = 0;
  in->peeked_char = 0;
  if (options == NULL)
	in->split_textarea = 1;		/* For backward compatibility */
  else
	in->split_textarea = options->split_textarea;
#ifdef NOTDEF
  in->debug_fp = NULL;
#endif

  return(in);
}

/*
 * Return 1 if EOF has been reached, -1 if an error has occurred, 0 otherwise.
 */
int
cgiparse_is_end(CGI_input *in)
{

  if (in->got_peeked_char)
	return(0);

  if (in->fp != NULL) {
	if (ferror(in->fp))
	  return(-1);
	return(feof(in->fp) || (in->have_length && in->rem == 0));
  }

  return(in->ptr == NULL || in->rem == 0);
}

/*
 * Get the next character and store it in buffer CH.
 * Return -1 if an error occurs, 0 upon EOF, and 1 otherwise.
 */
int
cgiparse_next_char(CGI_input *in, char *ch)
{
  int rc;
  
  if ((rc = cgiparse_is_end(in)) == -1)
	return(-1);
  if (rc == 1)
	return(0);

  rc = 1;
  if (in->got_peeked_char) {
	*ch = in->peeked_char & 0377;
	in->got_peeked_char = 0;
  }
  else {
	if (in->fp != NULL) {
	  if (fread(ch, 1, 1, in->fp) != 1) {
		rc = -1;
		if (feof(in->fp))
		  rc = 0;
	  }
	}
	else
	  *ch = *in->ptr++;
  }

  if (rc == 1 && in->have_length)
	in->rem--;

  if (debug_fp != NULL) {
	if (rc != 1) {
	  fclose(debug_fp);
	  debug_fp = NULL;
	}
	else
	  fputc(*ch & 0377, debug_fp);
  }

  return(rc);
}

/*
 * Peek at the next input character and store it in buffer CH.
 * Return -1 if an error occurs, 0 upon EOF, and 1 otherwise.
 */
int
cgiparse_peek_next_char(CGI_input *in, char *ch)
{
  int rc;

  if (in->got_peeked_char) {
	log_msg((LOG_ERROR_LEVEL, "Attempt to peek ahead too far"));
	return(-1);
  }

  if ((rc = cgiparse_is_end(in)) == -1)
	return(-1);
  if (rc == 1)
	return(0);

  rc = 1;
  if (in->fp != NULL) {
	if (fread(&in->peeked_char, 1, 1, in->fp) != 1) {
	  rc = -1;
	  if (feof(in->fp))
		rc = 0;
	}
  }
  else
	in->peeked_char = *in->ptr++;

  if (rc == 1) {
	in->got_peeked_char = 1;
	*ch = in->peeked_char;
  }

  return(rc);
}

int
cgiparse_urlencoded(CGI_input *in, Kwv *kwv)
{
  int i, n;
  char ch, *enc_kv, *k, *kv;

  if (debug_urlencode) {
	while (cgiparse_next_char(in, &ch) == 1)
	  fputc(ch & 0377, stdout);
	return(-1);
  }

  n = 0;
  while (!cgiparse_is_end(in)) {
	enc_kv = fmakeword(in, '&');
	plustospace(enc_kv);
	if ((kv = url_decode(enc_kv, NULL, NULL)) == NULL)
	  return(-1);
	k = makeword(kv, '=', NULL);
	if (*k == '\0') {
	  if (*kv == '\0') {
		/*
		 * If there's no 'name' and no 'value' part, we will just ignore
		 * this ("&&").
		 */
		continue;
	  }
	  /* There's no 'name' but there is a 'value' ("=foo"); what to do? */
	  log_msg((LOG_ERROR_LEVEL, "Invalid query string component: \"%s\"",
			   enc_kv));
	  return(-1);
	}
	if (kwv_add(kwv, k, kv) == NULL)
	  return(-1);

	if (in->split_textarea) {
	  /*
	   * A TEXTAREA may be split into multiple lines, separated by
	   * <CR><LF>.
	   * Turn those into multiple keyword/value pairs.
	   */
	  while ((i = strindex(kv, (int) '\r')) != -1 && kv[i + 1] == '\n') {
		kv[i] = '\0';
		if (kv[i + 2] == '\0')
		  break;
		if (kwv_add(kwv, k, &kv[i + 2]) == NULL)
		  return(-1);
		n++;
	  }
	}
  }

  if (debug_urlencode) {
	Kwv_pair *pair;
	Kwv_iter *iter;

	iter = kwv_iter_begin(kwv, NULL);
	while ((pair = kwv_iter_next(iter)) != NULL)
	  log_msg((LOG_DEBUG_LEVEL, "%s='%s'", pair->name, pair->val));
	free(iter);
  }

  return(0);
}

static char *
makeword(char *line, char stop, int *stopseen)
{
  int x = 0, y;
  char *word = ALLOC_N(char, strlen(line) + 1);

  for (x = 0; ((line[x]) && (line[x] != stop)); x++)
	word[x] = line[x];

  if (stopseen != NULL)
	*stopseen = (line[x] == stop);

  word[x] = '\0';
  if (line[x])
	++x;
  y = 0;

  while ((line[y++] = line[x++]))
	;

  return(word);
}

static char *
fmakeword(CGI_input *in, char stop)
{
  int wsize;
  char ch, *word;
  int ll;

  wsize = 16;
  ll = 0;
  word = ALLOC_N(char, wsize);

  while (1) {
	if (cgiparse_next_char(in, &ch) != 1) {
	  word[ll] = '\0';
	  break;
	}
	word[ll] = ch;
	if (ll == (wsize - 1)) {
	  wsize *= 2;
	  word = (char *) realloc(word, sizeof(char) * wsize);
	}
	if ((word[ll] == stop) || cgiparse_is_end(in)) {
	  if (word[ll] != stop)
		ll++;
	  word[ll] = '\0';
	  word = (char *) realloc(word, ll + 1);
	  break;
	}
	++ll;
  }

  if (debug_urlencode)
	log_msg((LOG_DEBUG_LEVEL, "word='%s'", word));

  return(word);
}

/*
 * Replace '+' with a space, IN PLACE.
 * From the WWW Common Gateway Interface, Version 1.2 draft:
 *   An alternate "shortcut" encoding for representing the space character
 *   exists and is in common use. Scripts should be prepared to recognise
 *   both '+' and '%20' as an encoded space in a URL.
 * http://cgi-spec.golux.com/cgi-120-00a.html
 */
static void
plustospace(char *str)
{
  int x;

  for (x = 0; str[x] != '\0'; x++) {
	if (str[x] == '+')
	  str[x] = ' ';
  }
}

static char *
resolver(char *name, void *arg)
{
  char *p;
  
  if ((p = getenv(name)) == NULL)
	return(NULL);
  return(p);
}

/*
 * Parse query parameters and an entity-body.
 * If FP isn't NULL and there is CONTENT_LENGTH, then POST parameters
 * will be processed.
 * If a query string is provided, it will be processed regardless of whether
 * POST parameters are also present.
 * KWV is assumed to have been initialized; processed parameters are
 * appended to KWV.
 * Return -1 on error, otherwise the number of parameters that were added
 * to KWV.
 */
int
cgiparse(FILE *fp, char *query_string, Kwv *kwv, Mime **mp)
{
  int have_entity_body, orig_npairs;
  unsigned long clen;
  char *content_type, *request_method;
  CGI_input *input;
  Http_method method;
  Mime *mime;
  Mime_content_type *ct;

  orig_npairs = kwv->npairs;

  if (mp != NULL)
	*mp = NULL;

  if (test_mime) {
	/* Purely for testing */
	mime = ALLOC(Mime);
	input = cgiparse_set_input(HTTP_POST_METHOD, stdin, NULL, 0, 0, NULL);
	mime_init(mime, resolver, NULL);
	mime_parse_message_headers(mime, input);
	mime_parse(mime, input, kwv);
	return(0);
  }

  entity_body.content_type = NULL;
  entity_body.request_method = NULL;
  entity_body.content_length = 0;
  entity_body.length = 0;
  entity_body.body = NULL;

  /*
   * The presence of a Content-Length or Transfer-Encoding header
   * indicates that an entity-body is being sent (RFC 2616).
   * Does Apache pass us the Transfer-Encoding header?
   * It apparently doesn't matter because "Servers MUST provide this
   * [CONTENT_LENGTH] metavariable to scripts if the request was accompanied by
   * a message-body entity".
   */
  if (cgiparse_get_content_length(&clen) == 1 && clen > 0) {
	have_entity_body = 1;
	log_msg((LOG_TRACE_LEVEL, "Content-Length is %u", clen));
  }
  else {
	have_entity_body = 0;
	clen = 0;
	log_msg((LOG_TRACE_LEVEL, "Content-Length is absent or zero"));
  }

  request_method = getenv("REQUEST_METHOD");
  log_msg((LOG_TRACE_LEVEL, "Method is %s",
		   (request_method == NULL) ? "absent" : request_method));
  if (request_method == NULL)
	request_method = "GET";		/* Useful when debugging */
  method = http_string_to_method(request_method);

  /* See RFC 2616 */
  mime = ALLOC(Mime);
  mime_init(mime, resolver, NULL);

  content_type = getenv("CONTENT_TYPE");
  if (content_type != NULL) {
	log_msg((LOG_TRACE_LEVEL, "Content-Type is \"%s\"", content_type));
	ct = &mime->content_type;
	mime_parse_content_type(content_type, ct);
  }
  else {
	log_msg((LOG_TRACE_LEVEL, "Content-Type is absent"));
	ct = NULL;
  }

  if (have_entity_body && fp != NULL) {
	if (clen > 0)
	  input = cgiparse_set_input(method, fp, NULL, clen, 1, NULL);
	else
	  input = cgiparse_set_input(method, fp, NULL, 0, 0, NULL);

	if (ct != NULL
		&& ((ct->content_type == MIME_TYPE_APPLICATION
			 && strcaseeq(ct->subtype, "x-www-form-urlencoded"))
			|| (ct->content_type == MIME_TYPE_MULTIPART
				/* The semicolon may be absent. */
				&& (strcaseeq(ct->subtype, "form-data;")
					|| strcaseeq(ct->subtype, "form-data"))))) {
	  if (mime_parse(mime, input, kwv) == -1) {
		log_msg((LOG_ERROR_LEVEL, "MIME parse of entity-body failed"));
		return(-1);
	  }
	} else if (have_entity_body) {
	  char ch;
	  Ds ds;

	  log_msg((LOG_TRACE_LEVEL, "There is non-FORM data..."));
	  ds_init(&ds);
	  while (cgiparse_next_char(input, &ch) == 1)
		ds_appendc(&ds, ch);
	  ds_appendc(&ds, (int) '\0');
	  entity_body.content_type = content_type;
	  entity_body.request_method = request_method;
	  entity_body.content_length = clen;
	  entity_body.length = ds_len(&ds);
	  entity_body.body = ds_buf(&ds);

	  log_msg((LOG_TRACE_LEVEL, "Content-Type: %s, Content-Length: %d",
			   entity_body.content_type, entity_body.content_length));
	  log_msg((LOG_TRACE_LEVEL, "Actual Length: %d, Method: %s",
			   entity_body.length, entity_body.request_method));
	  log_msg((LOG_TRACE_LEVEL, "entity:\n%s", ds_buf(&ds)));
	}
  }

  /*
   * If there's a query string, process it regardless of whether there's
   * an entity-body.
   */
  if (query_string != NULL && *query_string != '\0') {
	input = cgiparse_set_input(HTTP_GET_METHOD, NULL, query_string,
							   strlen(query_string), 1, NULL);
	mime_parse_content_type("application/x-www-form-urlencoded",
							&mime->content_type);
	if (mime_parse(mime, input, kwv) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "mime_parse of query string failed"));
	  return(-1);
	}
  }

  if (mp != NULL)
	*mp = mime;

  return(kwv->npairs - orig_npairs);
}

Kwv *
cgiparse_string(char *query_string, Kwv *k, CGI_options *options)
{
  CGI_input *input;
  Kwv *kwv;
  Mime *mime;

  mime = ALLOC(Mime);
  mime_init(mime, NULL, NULL);

  input = cgiparse_set_input(HTTP_GET_METHOD, NULL, query_string,
							 strlen(query_string), 1, options);
  mime_parse_content_type("application/x-www-form-urlencoded",
						  &mime->content_type);
  if (k != NULL)
	kwv = k;
  else
	kwv = kwv_init(8);

  if (mime_parse(mime, input, kwv) == -1) {
	log_msg((LOG_ERROR_LEVEL, "mime_parse of query string failed"));
	return(NULL);
  }

  return(kwv);
}

#ifdef PROG

#include "local.h"

static void
usage(void)
{

  fprintf(stderr,
		  "Usage: cgiparse [-text|-html|-one|-arg var|-sh|-targ var]\n");
  fprintf(stderr,
		  "  [-checkdup] [-d] [-nodups | -duperror] [-qs query-string] [-copy file]\n");
  fprintf(stderr,
		  "  [-n name file] [-enc none|url|mime|dacs] [-in file] [-nonewline]\n");
  fprintf(stderr, "Where \"-n name file\" writes decoded form-data parts\n");
  fprintf(stderr, "  with a name parameter having value <name> to <file>\n");

  if (debug_fp != NULL)
	fclose(debug_fp);

  exit(1);
}

typedef enum {
  CGIPARSE_ENC_NONE = 0,
  CGIPARSE_ENC_URL  = 1,
  CGIPARSE_ENC_M64  = 2,
  CGIPARSE_ENC_D64  = 3
} Cgiparse_enc;

static Ds *
decode_val(char *enc_val, Cgiparse_enc enc_type)
{
  int non_printable;
  unsigned char *decoded;
  long vlen;
  unsigned int nbytes;
  Ds *ds;

  switch (enc_type) {
  case CGIPARSE_ENC_URL:
	decoded = (unsigned char *) url_decode(enc_val, &non_printable, &ds);
	if (decoded == NULL)
	  return(NULL);
	break;

  case CGIPARSE_ENC_M64:
	if ((vlen = mime_decode_base64(enc_val, &decoded)) == -1)
	  return(NULL);
	ds = ds_setn(NULL, decoded, (size_t) vlen);
	break;

  case CGIPARSE_ENC_D64:
	if ((decoded = stra64b(enc_val, NULL, &nbytes)) == NULL)
	  return(NULL);
	ds = ds_setn(NULL, decoded, (size_t) nbytes);
	break;

  case CGIPARSE_ENC_NONE:
  default:
	ds = ds_setn(NULL, (unsigned char *) enc_val, strlen(enc_val));
	break;
  }

  ds_appendc(ds, (int) '\0');

  return(ds);
}

static char *
shell_assign(char *var_name, Ds *var_value)
{
  char *assn, *errmsg;

  if (!strprintable(var_name, 0, 0)
	  || !strprintable(ds_buf(var_value), ds_len(var_value) - 1, 0))
	return(NULL);

  /*
   * A user-set sh variable name "must have a name consisting solely of
   * alphabet-ics, numerics, and underscores.  The first letter of a
   * variable name must not be numeric."
   */
  errmsg = NULL;
  if (strregex(var_name, "[a-z][a-z0-9_]*", NULL, REG_ICASE | REG_NOSUB,
			   NULL, &errmsg) < 1)
	return(NULL);

  assn = ds_xprintf("%s='%s';", var_name, strquote(ds_buf(var_value), "'"));

  return(assn);
}

static void
emit_html(FILE *ofp, Kwv *kwv)
{
  FILE *fp;
  Kwv_iter *iter;
  Kwv_pair *pair;

  if (kwv == NULL)
    return;

  kwv->error_msg = NULL;
  if (ofp != NULL)
	fp = ofp;
  else
    fp = stderr;

  fprintf(fp, "<br/>\n");
  iter = kwv_iter_begin(kwv, NULL);
  while ((pair = kwv_iter_next(iter)) != NULL) {
    attribute_hl(fp, NULL, pair->name, pair->val);
	fprintf(fp, "<br/>");
  }
  kwv_iter_end(iter);
}

int
main(int argc, char **argv)
{
  int mime_arg, html, i, onevalue, shell, suppress_newline, arg_test;
  int check_dup, debug_flag, dup_error, saw_mode_arg;
  char *infile, *p, *query, *query_string, *remote_addr;
  Cgiparse_enc enc_type;
  Kwv *kwv;
  Kwv_dup_mode dup_mode;
  Log_desc *ld;
  Mime *mime;

  saw_mode_arg = 0;
  suppress_newline = 0;
  arg_test = 0;
  html = 0;
  shell = 0;
  onevalue = 0;
  check_dup = 0;
  query = NULL;
  query_string = getenv("QUERY_STRING");
  enc_type = CGIPARSE_ENC_NONE;
  infile = NULL;
  mime = NULL;
  debug_flag = 0;

  remote_addr = getenv("REMOTE_ADDR");

  /*
   * Allow duplicate parameter names.
   */
  dup_mode = KWV_ALLOW_DUPS;
  dup_error = 0;

  /*
   * XXX add a mode where all varname/varvalue pairs are emitted;
   * varvalues need to be encoded
   */

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-arg")) {
	  if (++i == argc)
		usage();
	  query = argv[i];
	  html = 0;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-checkdup")) {
	  check_dup++;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-d"))
	  debug_flag++;
	else if (streq(argv[i], "-enc")) {
	  if (++i == argc)
		usage();
	  if (strcaseeq(argv[i], "none"))
		enc_type = CGIPARSE_ENC_NONE;
	  else if (strcaseeq(argv[i], "url"))
		enc_type = CGIPARSE_ENC_URL;
	  else if (strcaseeq(argv[i], "mime"))
		enc_type = CGIPARSE_ENC_M64;
	  else if (strcaseeq(argv[i], "dacs"))
		enc_type = CGIPARSE_ENC_D64;
	  else
		usage();
	}
	else if (streq(argv[i], "-html")) {
	  html = 1;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-in")) {
	  if (++i == argc)
		usage();
	  infile = argv[i];
	}
	else if (streq(argv[i], "-nodups"))
	  dup_mode = KWV_REPLACE_DUPS;
	else if (streq(argv[i], "-duperror")) {
	  dup_error = 1;
	  dup_mode = KWV_NO_DUPS;
	}
	else if (streq(argv[i], "-nonewline"))
	  suppress_newline = 1;
	else if (streq(argv[i], "-one")) {
	  onevalue = 1;
	  html = 0;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-qs")) {
	  if (++i == argc)
		usage();
	  query_string = argv[i];
	}
	else if (streq(argv[i], "-sh") || streq(argv[i], "--shell")) {
	  shell = 1;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-targ")) {
	  if (++i == argc)
		usage();
	  arg_test = 1;
	  query = argv[i];
	  html = 0;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "-text")) {
	  html = 0;
	  saw_mode_arg++;
	}
	else if (streq(argv[i], "--version")) {
	  dacs_version(stderr);
	  exit(0);
	}
	else
	  break;
  }

  if (!debug_flag) {
	ld = log_init(NULL, 0, NULL, "mm", LOG_NONE_LEVEL, NULL);
	log_set_level(ld, LOG_WARN_LEVEL);
	log_set_desc(ld, LOG_DISABLED);
  }

  /* If there is no mode argument, -text is the default. */
  if (saw_mode_arg > 1) {
	fprintf(stderr, "Too many mode arguments\n");
	usage();
	/*NOTREACHED*/
  }

  mime_arg = i;
  while (i < argc) {
	if (streq(argv[i], "-copy")) {
	  if (++i != argc) {
		if ((debug_fp = fopen(argv[i], "a")) == NULL)
		  fprintf(stderr, "Can't write to debug file '%s'", argv[i]);
	  }
	}
	else if (streq(argv[i], "-n")) {
	  if (++i == argc)
		usage();
	  if (++i == argc)
		usage();	  
	}
	else
	  usage();
	i++;
  }

  if (html && remote_addr != NULL)
	emit_html_header(stdout, NULL);

  kwv = kwv_init(100);
  kwv->dup_mode = dup_mode;

  /*
   * If an input file was specified, read name/value pairs from it,
   * otherwise parse a query string read from stdin.
   */
  if (infile != NULL) {
	Ds *ds;
	Kwv_conf conf = {
	  " ", NULL, NULL, KWV_CONF_DEFAULT, "\n", 10, NULL, NULL
	};

	if (streq(infile, "-"))
	  ds = ds_load_file(NULL, NULL);
	else
	  ds = ds_load_file(NULL, infile);

	if (ds == NULL) {
	  if (html)
		printf("<p>Cannot open \"%s\".\n", infile);
	  else
		fprintf(stderr, "Cannot open \"%s\".\n", infile);
	  exit(1);
	}

	if (kwv_make_sep(kwv, ds_buf(ds), &conf) == NULL) {
	  if (html)
		printf("<p>Fatal parameter parsing error.\n");
	  else
		fprintf(stderr, "Fatal parameter parsing error\n");
	  exit(1);
	}
  }
  else {
	if (cgiparse(stdin, query_string, kwv, &mime) == -1) {
	  if (html)
		printf("<p>Fatal parameter parsing error.\n");
	  else
		fprintf(stderr, "Fatal parsing error\n");
	  exit(1);
	}
  }

  if (check_dup) {
	for (i = 0; i < kwv->nused; i++) {
	  if (kwv->pairs[i]->next != NULL)
		exit(1);
	}

	exit(0);
  }

  /*
   * In the query mode, only one specified parameter name is considered.
   */
  if (query != NULL) {
	Kwv_pair *pair;

	/* Emit a variable or test for its existence. */
	if (kwv->nused == 0)
	  exit(1);

	if ((pair = kwv_lookup(kwv, query)) != NULL) {
	  Ds *ds;

	  /*
	   * multipart-formdata is a special case because the variable's normal
	   * value is the empty string but its xval is the MIME base-64 encoding
	   * of its value.
	   */
	  if (*pair->val == '\0' && pair->xval != NULL)
		p = pair->xval;
	  else
		p = pair->val;

	  if ((ds = decode_val(p, enc_type)) == NULL)
		exit(1);

	  if (arg_test)
		exit(0);

	  if (shell) {
		if ((p = shell_assign(query, ds)) == NULL)
		  exit(1);
		printf("%s\n", p);

		exit(0);
	  }

	  write(1, ds_buf(ds), ds_len(ds) - 1);
	  if (!suppress_newline)
		write(1, "\n", 1);

	  exit(0);
	}

	exit(1);
  }

  /*
   * In the non-query modes, all parameters are output in some fashion.
   */
  if (kwv->nused > 0) {
	if (html) {
	  printf("CGI parameters:<br>");
	  emit_html(stdout, kwv);
	}
	else if (onevalue) {
	  Kwv_iter *iter;
	  Kwv_pair *pair;

	  iter = kwv_iter_begin(kwv, NULL);
	  while ((pair = kwv_iter_next(iter)) != NULL) {
		if (pair->val[0] == '\0' && pair->xval != NULL)
		  p = pair->xval;
		else
		  p = pair->val;
		printf("%s\n", p);
	  }
	  kwv_iter_end(iter);
	}
	else if (shell) {
	  char *varname;
	  Ds *ds;
	  Kwv_iter *iter;
	  Kwv_pair *pair;

	  iter = kwv_iter_begin(kwv, NULL);
	  while ((pair = kwv_iter_next(iter)) != NULL) {
		varname = pair->name;
		if (pair->val[0] == '\0' && pair->xval != NULL)
		  p = pair->xval;
		else
		  p = pair->val;

		if ((ds = decode_val(p, enc_type)) == NULL)
		  exit(1);
	  
		if ((p = shell_assign(varname, ds)) == NULL)
		  exit(1);

		printf("%s", p);
	  }
	  kwv_iter_end(iter);
	  printf("\n");
	}
	else {
	  Kwv_iter *iter;
	  Kwv_pair *pair;

	  iter = kwv_iter_begin(kwv, NULL);
	  while ((pair = kwv_iter_next(iter)) != NULL) {
		if (pair->val[0] == '\0' && pair->xval != NULL) {
		  p = pair->xval;
		  printf("%s %s\n", pair->name, p);
		  continue;
		}

		switch (enc_type) {
		case CGIPARSE_ENC_URL:
		  p = url_encode(pair->val, 0);
		  break;

		case CGIPARSE_ENC_M64:
		  mime_encode_base64((unsigned char *) pair->val,
							 strlen(pair->val), &p);
		  break;

		case CGIPARSE_ENC_D64:
		  strba64((unsigned char *) pair->val, strlen(pair->val), &p);
		  break;

		case CGIPARSE_ENC_NONE:
		default:
		  p = pair->val;
		  break;
		}

		printf("%s %s\n", pair->name, p);
	  }
	  kwv_iter_end(iter);
	}
  }

  if (html && remote_addr != NULL)
	emit_html_trailer(stdout);

  if (debug_fp != NULL)
	fclose(debug_fp);

  for (i = mime_arg; i < argc; i++) {
	if (streq(argv[i], "-copy"))
	  i++;
	else if (streq(argv[i], "-n")) {
	  char *file, *name;
	  void *outs;
	  long outlen;
	  Mime_part *mp;
	  Mime_content_disposition *mcd;
	  FILE *fp;

	  name = argv[++i];
	  file = argv[++i];
	  if (mime == NULL)
		exit(1);
	  for (mp = mime->parts; mp != NULL; mp = mp->next) {
		mcd = mp->disposition;
		if (mcd == NULL || mcd->disposition != MIME_DISPOSITION_FORMDATA)
		  continue;
		if (mcd->name != NULL && !streq(mcd->name, name))
		  continue;
		if (mp->body == NULL)
		  break;
		fprintf(stderr, "Writing \"%s\" to %s\n", name, file);
		if (mp->encoding == MIME_ENCODING_QUOTEDPRINTABLE) {
		  outlen = mime_decode_quotedprintable(ds_buf(mp->body),
											   (char **) &outs);
		  if (outlen == -1) {
			fprintf(stderr, "Decoding quotedprintable failed - ignoring\n");
			continue;
		  }
		}
		else if (mp->encoding == MIME_ENCODING_BASE64) {
		  outlen = mime_decode_base64(ds_buf(mp->body),
									  (unsigned char **) &outs);
		  if (outlen == -1) {
			fprintf(stderr, "Decoding base64 failed - ignoring\n");
			continue;
		  }
		}
		else {
		  outlen = ds_len(mp->body);
		  outs = (void *) ds_buf(mp->body);
		}
		
		if ((fp = fopen(file, "w")) == NULL) {
		  fprintf(stderr, "Couldn't write to %s - ignoring \n", file);
		  continue;
		}
		if (fwrite(outs, outlen, 1, fp) != 1)
		  fprintf(stderr, "Failed to fwrite %ld bytes: %s\n",
				  outlen, strerror(errno));
		else
		  fprintf(stderr, "Wrote %ld bytes\n", outlen);
		fclose(fp);
	  }
	}
  }

  exit(0);
}
#endif
