[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
/*
 * CGI script for sending mail, driven by a template file that inserts
 * form fields at specified spots.
 *
 * Usage:
 *   Create HTML form that specifies this script as the action with the
 *   template file as the path_info following the script name.
 *
 * Template file format:
 *    Template file consists of a header followed by the body section, the
 *    end of header is indicated by a blank line.  At any point in the
 *    file you can specify a form field name surrounded by square brackets
 *    and the corresponding field from the form will be substituted.
 *
 * Template header fields:
 *    To: address
 *    Subject: string
 *    Success: url
 *    
 *    Fail: url
 * 
 *  AUTHORS:
 *
 *      Dick Munroe			Original CGIMAILTO program.
 *	Acorn Software, Inc.
 *	munroe@acornsw.com
 *
 *      David Jones
 *	Ohio State University
 *
 *  CREATION DATE:  04-Dec-94
 * Date: 13-JUN-1996
 * Revised: 16-JUN-1996
 * Revised:  30-MAY-1997
 */
#if defined(__ALPHA)
/* Some structures needed by mail are not aligned */
#pragma nomember_alignment
#endif

/** MGD **/
#if defined(__ia64)
#pragma nomember_alignment
#endif

#include <stdio.h>
#include <stat.h>

#include "cgilib.h"
struct ItemList { short int length, code; char *buffer; long returnLength; };

#include <descrip.h>
#include <jpidef.h>
#include <lib$routines.h>
#include <maildef.h>
#include <ssdef.h>
#include <stdlib.h>
#include <str$routines.h>
#include <string.h>
#include <ctype.h>
#include <stsdef.h>
/*
 * Prototypes for callable mail API.
 */
extern unsigned long mail$send_begin() ;
extern unsigned long mail$send_add_attribute() ;
extern unsigned long mail$send_add_address() ;
extern unsigned long mail$send_add_bodypart() ;
extern unsigned long mail$send_message() ;
extern unsigned long mail$send_end() ;
/*
 * Miscellaneous data structures used to store form data and text lines.
 */
struct field_def { struct field_def *next; char *key, *value;
	int klen; };
typedef struct field_def *field_def_p;
struct scanctx {
    char *body;
    char *subline;
};
struct outrec {
    struct outrec *next;
    int length;
    char data[1];	/* variable length actually allocated */
};
static int ent_flag = 0;	/* if true, entify chars "<>&" in expansions */
/*
 * Forward function references.
 */
static int scan_body ( struct scanctx *context, field_def_p fields,
	char *buffer, int bufsize, int *length );
static char *expand_line ( char *, field_def_p );
static char *reformat_value ( char *source, char *format, int fmt_len );
static char *entify_string ( char *source );
static field_def_p form_data();
static void send_fail ( char *stsline, char *reason, void *arg )
{
    char fmt[256];
    sprintf(fmt,"context-type: text/plain\nstatus: %s\n\nSend failed: %s\n", 
		stsline, reason );
    cgi_printf(fmt,arg,arg);
    exit(1);
}
/****************************************************************************/
/* Main routine.
 */
int main ( int argc, char **argv )
{
    char buffer[1024], body_line[256];
    char *template_name, *template_file, *template;
    char *method, *personal_name, *t_body, *expanded, *cp ;
    struct scanctx context;
    struct ItemList itemList[10], nullItemList = {0, 0, 0, 0} ;
    unsigned long sendContext = 0 ;
    char *to, *subj, *version, *success, *failure, *line;
    FILE *tfile;
    field_def_p field_list, fld;
    struct outrec rec_list, *last_rec, *cur_rec;
    int i, j, status, length, t_used, t_alloc, succ_sts;
    /*
     * Initialize CGI and load content into memory.
      */
    status = cgi_init ( argc, argv );
    if ( (status&1) == 0 ) return status;
    field_list = form_data();
    if ( !field_list ) return 1;
    /*
     * Get template file name and attempt to open.
     */
    template_name = cgi_info ( "PATH_INFO" );
    template_file = cgi_info ( "PATH_TRANSLATED" );
    if ( !template_name || !template_file ) 
	send_fail ( "500 bad template", "Bad template name (%s)\n",
		template_name ? template_name : "???" );

    tfile = fopen ( template_file, "r" );
    if ( !tfile ) send_fail ( "500 open failure",
	"open failure on template file (%s)", template_file );
    /*
     * Read template data into memory.
     */
    t_alloc = 8192;
    template = malloc(t_alloc);
    t_used = 0;
    while (0 < (length=fread(&template[t_used], 1, t_alloc - t_used, tfile))) {
	t_used += length;
	if ( t_used >= t_alloc ) {
	    t_alloc = t_alloc + 20000;
	    if ( t_alloc > 100000 ) send_fail ( "500 error",
		"template file too big (%s)", template_name );
	    template = realloc ( template, t_alloc );
	}
    }
    fclose ( tfile );
    /*
     * Parse header out of template, check that first line is version.
     */
    t_body = version = (char *) 0;
    for ( i = 0; i < t_used; i++ ) if ( template[i] == '\n' ) {
	if ( !version ) {
	    /* First line must be "tmail" */
	    for ( j = 0; template[j] && template[j] != ':'; j++ ) 
			template[j] = tolower(template[j]);
	    if ( 0 != strncmp(template,"tmail:", 6) ) send_fail (
		"500 invalid template",
		"first line of template must be tmail: header", 0 );
	    version = &template[7];
	}
	if ( template[i+1] == '\n' ) {
	    template[i+1] = '\0';
	    t_body = &template[i+2];
	    break;
	}
    }
    if ( !t_body ) send_fail ( "500 invalid template", 
	"Invalid template format - no body", 0 );
    /*
     * Parse header lines.
     */
    to = subj = success = failure = (char *) 0;
    succ_sts = 0;
    for ( i = 0, line = template; template[i]; i++ ) {
	if (template[i] == '\n') {
	    template[i] = '\0';
	    for ( j = 0; line[j] && line[j] != ':'; j++ ) 
			line[j] = tolower(line[j]);
	    if ( strncmp(line,"to:",3)==0 ) 
		to = expand_line(&line[3],field_list);
	    else if ( strncmp(line,"subject:",8) == 0 ) 
		subj = expand_line(&line[8], field_list);
	    else if ( strncmp(line,"success:",8) == 0 )
		success = expand_line ( &line[8], field_list );
	    else if ( strncmp(line,"success-status:",15) == 0 )
		succ_sts = atoi(&line[15]);
	    else if ( strncmp(line,"failure:",8) == 0 )
		failure = expand_line ( &line[8], field_list );
	    line = &template[i+1];
	}
    }
    if ( !to ) send_fail ( "500 invalid template",
	"invalid template file header - no to: line", 0) ;
    /*
     * Parse the body lines and generate list of records.
     */
    context.body = t_body;
    context.subline = (char *) 0;

    for ( last_rec = &rec_list; ; last_rec  = cur_rec ) {
	status = scan_body (&context, field_list, body_line, 
		sizeof(body_line)-1, &length );
	if ( status > 0 ) {
	    /*
	     * Add record to list.
	     */
	    cur_rec = (struct outrec *) malloc (sizeof(struct outrec)+length);
	    last_rec->next = cur_rec;
	    if ( !cur_rec ) return 0;
	    cur_rec->next = (struct outrec *) 0;
	    cur_rec->length = length;
	    memcpy ( cur_rec->data, body_line, length );
	    cur_rec->data[length] = '\0';	/* not really necessary */
	} else if ( status == 0 ) {
	    break;	/* status == 0 ==> EOD */
	} else {
	    /*  Error */
	    return 1;
	}
    }
    /*
     * Initiate mail message, construct a personal name for the
     * message from the path used to invoke it.
     */
    personal_name = malloc ( strlen(template_name) + 20 );
    sprintf ( personal_name, "tmail: %s", template_name );
    i = -1;
    itemList[++i].length =		strlen(personal_name);
    itemList[i].code =			MAIL$_SEND_PERS_NAME ;
    itemList[i].buffer =		personal_name;
    itemList[i].returnLength =	0 ;
    itemList[++i].length =		0 ;
    itemList[i].code =			0 ;

    status =
	mail$send_begin (
	    &sendContext,
	    &itemList,
	    &nullItemList) ;		/* Set up to construct the mail context.				 */
    if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error",
		"MAIL$SEND_BEGIN returned %%X%0X",(void *) status) ;

    /*
    ** Now build up the attributes of the message.  
    */
    i = -1 ;
    while ( isspace(*to) && *to ) to++;		/* trim leading spaced */
    itemList[++i].length =		strlen(to) ;
    itemList[i].code =			MAIL$_SEND_TO_LINE ;
    itemList[i].buffer =		to ;
    itemList[i].returnLength =	0 ;

    if ( !subj ) subj = "Mail from tmail script";
    while ( isspace(*subj) && *subj ) subj++;	/* trim leading spaced */
    itemList[++i].length =		strlen(subj);
    itemList[i].code =			MAIL$_SEND_SUBJECT ;
    itemList[i].buffer =		subj;
    itemList[i].returnLength =	0 ;

    itemList[++i].length =		0 ;
    itemList[i].code =			0 ;

    status =
	mail$send_add_attribute (
	    &sendContext,
	    &itemList,
	    &nullItemList) ;				/* Add the the message header.						 */
    if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error",
	"MAIL$SEND_ADD_ATTRIBUTE returned %%X%0X,\ncheck template's subject: line", 
	(void *) status );
    /*
     * Set destination for to: header line.
     */
    i = -1 ;
    itemList[++i].length = strlen(to) ;
    itemList[i].code =	MAIL$_SEND_USERNAME ;
    itemList[i].buffer =		to ;
    itemList[i].returnLength =	0 ;
    itemList[++i] = nullItemList ;			/* The itemlist describes an address of a receipient.			 */

    status =
	mail$send_add_address (
	    &sendContext,
	    &itemList,
	    &nullItemList) ;				/* Add the sender of the message to the list of receipients.		 */
    if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", 
	"MAIL$SEND_ADD_ADDRESS returned %%X%0X,\ncheck template's to: header line", 
	(void *) status);
    /*
     * Send the items.
     */
    itemList[0].code = MAIL$_SEND_RECORD;
    itemList[0].returnLength = 0;
    itemList[1] = nullItemList;
    for ( cur_rec = rec_list.next; cur_rec; cur_rec = cur_rec->next ) {
	itemList[0].length = cur_rec->length;
        itemList[0].buffer = cur_rec->data;
        status = mail$send_add_bodypart 
			(&sendContext, &itemList, &nullItemList) ;
	if ( !$VMS_STATUS_SUCCESS(status) ) send_fail ( "500 mail error",
	    "MAIL$SEND_ADD_BODYPART returned %%X%0X", (void *) status) ;
    }
    /*
     * Message complete, queue to delivery system.
     */
    status =
	mail$send_message (
	    &sendContext,
	    &nullItemList,
	    &nullItemList) ;				/* Send the message.							 */
    if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error",
	"MAIL$SEND_MESSAGE returned %%X%0X", (void *) status) ;
	 
    status =
	mail$send_end (
	    &sendContext,
	    &nullItemList,
	    &nullItemList) ;	    			/* Set up to construct the mail context.				 */
    if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error",
	"MAIL$SEND_END returned %%X%0X", (void *) status);

    if ( success ) {
	cgi_printf ( "location: %s\n\n", success );
    } else if ( context.body[0] ) {
	/*
	 * Non-null body remaining means it holds data to return, assume
	 * it includes CGI headers.
	 */
	ent_flag = 1;
	while (  scan_body (&context, field_list, body_line, 
		sizeof(body_line)-1, &length ) > 0 ) {
	    cgi_printf ( "%s\n", body_line );
	}
    } else {
	cgi_printf("content-type: text/plain\n");	/* CGI header */
	if ( (succ_sts < 200) || (succ_sts > 299) ) succ_sts = 200;
	cgi_printf("status: %d mail delivered\n\n", succ_sts );
	cgi_printf("Sending form data as MAIL to: %s\n",to);
	cgi_printf("Send Succeeded: submitted form data mailed\n") ;
    }
    return 1;
}
/*****************************************************************************/
/* Scan template body for tags and expand from form fields data, return
 * result in line-oriented fashion.
 */
static int scan_body ( struct scanctx *context, field_def_p fields,
	char *buffer, int bufsize, int *length )
{
    int i;
    /*
     * Load next subline if none currently.
     */
    if ( !context->subline ) {
	/* scan next body line. */
	for ( i = 0; context->body[i] != '\n'; i++ ) {
	    if ( !context->body[i] ) return 0;	/* no more body */
	}
	context->body[i] = '\0';
	context->subline = expand_line ( context->body, fields );
	context->body[i] = '\n';
	context->body = &context->body[i+1];
	if ( !context->subline ) return 0;
    }
    /*
     * parse data out of subline.
     */
    for ( i = 0; i < bufsize; i++ ) {
	buffer[i] = context->subline[i];
	if ( buffer[i] == '\n' ) {
	    context->subline = &context->subline[i+1];
	    break;
	} else if ( context->subline[i] == '\r' ) {
	    if ( context->subline[i+1] == '\n' )
		context->subline = &context->subline[i+2];
	    else context->subline = &context->subline[i+1];
	    break;
	} else if ( !context->subline[i] ) {
	    context->subline += i;
	    break;
	}
    }
    buffer[i] = '\0';
    *length = i;
    if ( i >= bufsize ) context->subline = &context->subline[i];
    if ( context->subline[0] == '\0' ) { 
	/* Done processing subline, force load of next */
	context->subline = (char *) 0;
    }
    return 1;
}
/****************************************************************************/
/* Convert escaped characters in string */
static int htmlStrcpy (
    char *out,
    char *in)
{
    int	i, j;
    for ( j=0;  *in;  in++)
    {
    	if (*in == '%') {
	    int	xxx ;
	    if ( (in[1]=='\0') || (in[2]=='\0') ) break;  /* too short */
	    sscanf (&in[1], "%02x", &xxx) ;
	    out[j++] = (char) xxx ;
	    in += 2 ;
	} else {
	    out[j++] = *in;
	}
    }
    return j;
}
/****************************************************************************/
/* Read form data and parse into list of key/value pairs.  Plus-to-space
 * conversion is performed but escaped chars are not converted.
 * Full CGI responses are generated on error.
 */
static field_def_p form_data()
{
    char *method, *content_data, *cp;
    field_def_p field_list, fld;
    int i, content_length, status;
    /*
     * Determine what form data is being delivered by client.
     */
    method = cgi_info ( "REQUEST_METHOD" );
    if ( 0 == strcmp ( method, "POST" ) ) {
	/*
	 * Data was posted.
	 */
        content_length = atoi(cgi_info ("CONTENT_LENGTH"));
        if (content_length == 0) send_fail ( "500 missing content", 
		"No data in CONTENT_LENGTH (%s)", cgi_info("CONTENT_LENGTH") );

        content_data = (char *) malloc(sizeof(char)*(content_length+1));
        status = cgi_read(content_data, content_length);
        if (status == 0) send_fail ( "500 missing content", 
		"No data from cgi_read", 0 );

        content_data[content_length] = '\0';
    } else if ( 0 == strcmp ( method, "GET" ) ) {
	/*
	 * Form method was GET, data is supplied as query string.
	 */
	cp = cgi_info ( "QUERY_STRING" );
	if ( !cp ) send_fail ( "500 missing content",
	    "no data from cgi_read\n", 0 ) ;

	if ( *cp == '?' ) cp++;
	content_length = strlen ( cp );
	content_data = (char *) malloc ( content_length + 1 );
	strcpy ( content_data, cp );

	/* printf("GET Content: '%s' l: %d\n", content_data, content_length);*/
    } else {
	send_fail("501 unsupported method","Method %s is unsupported",
	    method );
    }
    /*
     * Parse the content into key/value pairs.
     */
    field_list = (field_def_p) 0;
    if ( content_data[0] != '&' ) {
	fld = (field_def_p) malloc ( sizeof(struct field_def) );
	fld->next = field_list;
	fld->key = content_data;
	fld->klen = 0;
	field_list = fld;
    }
    for ( i = 0; i < content_length; i++ ) if ( content_data[i] == '&' ) {
	fld = (field_def_p) malloc ( sizeof(struct field_def) );
	fld->next = field_list;
	fld->key = &content_data[i+1];
	fld->klen = 0;
	content_data[i] = '\0';
	field_list = fld;
    }
    for ( fld = field_list; fld; fld = fld->next ) {
	fld->value = "";
	for ( cp = fld->key, i = 0; cp[i]; i++ ) {
	    if ( cp[i] == '=' ) {
		cp[i] = '\0';
		fld->klen = i;
		fld->value = &cp[i+1];
	    }
	}
	for ( cp = fld->value; *cp; cp++ ) if ( *cp == '+' ) *cp = ' ';
	/* printf("Parsed key '%s' = '%s'\n", fld->key, fld->value ); */
    }

    return field_list;
}
/*****************************************************************************/
/* Scan line for tags ([tag-name]) and replace with the corresponding value
 * from flist or cgi_info for that tag.
 */
static char *expand_line ( char *line, field_def_p flist )
{
    char *new;
    field_def_p fld;
    int i, j, k, size, length, lbrack, rbrack;
    /*
     * Allocate buffer to hold result, use size+256 as initial guess.
     */
    size = 256 + strlen ( line );
    new = malloc ( size );
    /*
     * Scan string for tags.
     */
    for ( i = j = 0; line[i]; i++ ) {
	if ( line[i] == '[' ) {
	    /*
	     * Start of tag detected
	     */
	    if ( line[i+1] == '[' ) {
		/* Treat "[[" specially: insert single "[" */
		k = 1;
		if ( j+1 >= size ) {
		    size += 256;
		    new = realloc ( new, size );
		}
		new[j++] = '[';
	    } else if ( line[i+1] == ']' ) {
		/* Treat [] specially: insert single "]" */
		k = 1;
		if ( j+1 >= size ) {
		    size += 256;
		    new = realloc ( new, size );
		}
		new[j++] = ']';
	    } else for ( k = 1; line[i+k]; k++) if ( line[i+k] == ']' ) {
		/*
		 * Found end of tag.
		 */
		int saved_j = j;
		if ( line[i+1] == '%' && (k < 65) && (k > 1) ) {
		    /*
		     * string following % is CGI environment variable
		     * unless it is %end.
		     */
		    char varname[64], *var;
		    strncpy ( varname, &line[i+2], k-2 );
		    varname[k-2] = '\0';
		    if ( varname[0] == '%' ) {	/* special variable */
			int l;
			for (l=0; varname[l]; l++) 
				varname[l] = tolower(varname[l]);
			length = 0;
			if ( strcmp ( varname, "%end" ) == 0 ) {
			    /* Magic tag to force EOD */
			    free ( new );
			    return (char *) 0;
			} else if ( strcmp ( varname, "%noentify" ) == 0 ) {
			    ent_flag = 0;
			} else if ( strcmp ( varname, "%entify" ) == 0 ) {
			    ent_flag = 1;
			}
		    } else { 			/* CGI variable */
		        var = cgi_info ( varname );
		        if ( !var ) var = "%unknown";
		        if ( ent_flag ) var = entify_string ( var );
		        length = strlen(var);
		        if ( length+j+1 >= size ) {
		            size = j + length + 256;
		            new = realloc ( new, size );
		        }
		        strcpy ( &new[j], var );
		    }
		    j += length;
		} else {
		    /*
		     * Check tag for format specifiers:
		     *    tag:string	value is string or null
		     *    tag?string    value is string if field 'on'
		     */
		    int klen, fmt_flag;
		    for (klen=fmt_flag=0; klen<k-1; klen++ ) if ( 
			line[i+klen+1] == ':' || line[i+klen+1] == '?' ) {
			fmt_flag = 1; break;
		    }
		    /*
		     * Scan fields supplied by caller for match.
		     */
		    for (fld=flist; fld; fld=fld->next) if (fld->klen==klen ) {
		        if ( strncmp(&line[i+1],fld->key,klen) == 0 ) {
			    char *value;
			    value = fld->value;
			    if ( fmt_flag ) value = reformat_value ( value,
				&line[i+klen+1], k-1-klen );
			    length = strlen ( value );
			    if ( length + j+1 >= size ) {
			        size = j + length + 256;
			        new = realloc ( new, size );
			    }
			    length = htmlStrcpy ( &new[j], value );
			    if ( ent_flag ) {
				/* Convert <s and >s */
				value = entify_string(&new[j]);
				if ( value != &new[j] ) {
				    length = strlen ( value );
				    if ( length + j+1 >= size ) {
					size = j + length + 256;
					new = realloc ( new, size );
				    }
				    strcpy ( &new[j], value );
				}
			    }
			    j += length;
			    if ( fmt_flag ) break;	/* only expand once */
		        }
		    }
		}
		if ( saved_j = j ) if ( strncmp("required-",&line[i+1],9)==0 ) {
		    /*
		     * Required field was missing, aobrt with error message.
		     */
		    send_fail ( "500 error", 
			"Required field not supplied: %s", &line[i] );
		}
		break;
	    }
	    if ( line[i+k] != '\0' ) i += k;
	} else {
	    /*
	     * Add character not part of tags to output.
	     */
	    if ( j+1 >= size ) {
		size = size + 256;
		new = realloc ( new, size );
	    }
	    new[j++] = line[i];
	}
    }
    /*
     * Ensure result is terminated and return in to caller.
     */
    new[j] = '\0';
    return new;
}
/****************************************************************************/
/* Substitute value according to format string.
 */
static char *reformat_value ( char *value, char *format, int fmt_len )
{
    char *result;
    /*
     * Perform tests based upon first character of format string,
     * returning empty string on test failure.
     */
    result = "";
    if ( *format == '?' ) {
	/* test value for 'on' */
	if ( tolower(value[0]) != 'o' ) return result;
	if ( tolower(value[1]) != 'n' ) return result;
	if ( value[2] != '\0' ) return result;
    } else if ( *format == ':' ) {
	/* test for non-null */
	if ( !*value ) return result;
    }
    /*
     * Copy chars in format string after 1st to result string.
     */
    result = malloc ( fmt_len );
    if ( !result ) return "";
    strncpy ( result, &format[1], fmt_len-1 );
    result[fmt_len-1] = '\0';

    return result;
}
/****************************************************************************/
/* Convert strings containing punctuation characters into escaped strings.
 */
static char *entify_string ( char *source )
{
   int i, j,brack_count;
   char *dest;
   dest = source;
   brack_count = 0;
   for ( i = 0; source[i]; i++ ) {
	if ( (source[i] == '<') || (source[i] == '>') ||
	    (source[i] == '&') ) brack_count++;
   }
   if ( brack_count > 0 ) {
	dest = malloc ( i + brack_count*4 + 1 );
	if ( !dest ) return source;

	for ( i = j = 0; source[i]; i++ ) {
	    if ( source[i] == '<' ) {
		dest[j++] = '&';
		dest[j++] = 'l';
		dest[j++] = 't';
		dest[j++] = ';';
	    } else if ( source[i] == '>' ) {
		dest[j++] = '&';
		dest[j++] = 'g';
		dest[j++] = 't';
		dest[j++] = ';';
	    } else if ( source[i] == '&' ) {
		dest[j++] = '&';
		dest[j++] = 'a';
		dest[j++] = 'm';
		dest[j++] = 'p';
		dest[j++] = ';';
	    } else
		dest[j++] = source[i];
	}
	dest[j] = '\0';
   }
   return dest;
}