/*++
/* NAME
/*	hfrom_format 3
/* SUMMARY
/*	Parse a header_from_format setting
/* SYNOPSIS
/*	#include <hfrom_format.h>
/*
/*	int	hfrom_format_parse(
/*	const char *name,
/*	const char *value)
/*
/*	const char *str_hfrom_format_code(int code)
/* DESCRIPTION
/*	hfrom_format_parse() takes a parameter name (used for
/*	diagnostics) and value, and maps it to the corresponding
/*	code: HFROM_FORMAT_NAME_STD maps to HFROM_FORMAT_CODE_STD,
/*	and HFROM_FORMAT_NAME_OBS maps to HFROM_FORMAT_CODE_OBS.
/*
/*	str_hfrom_format_code() does the reverse mapping.
/* DIAGNOSTICS
/*	All input errors are fatal.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>

 /*
  * Utility library.
  */
#include <name_code.h>
#include <msg.h>

 /*
  * Global library.
  */
#include <mail_params.h>

 /*
  * Application-specific.
  */
#include <hfrom_format.h>

 /*
  * Primitive dependency injection.
  */
#ifdef TEST
extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...);

#define msg_fatal test_msg_fatal
#endif

 /*
  * The name-to-code mapping.
  */
static const NAME_CODE hfrom_format_table[] = {
    HFROM_FORMAT_NAME_STD, HFROM_FORMAT_CODE_STD,
    HFROM_FORMAT_NAME_OBS, HFROM_FORMAT_CODE_OBS,
    0, -1,
};

/* hfrom_format_parse - parse header_from_format setting */

int     hfrom_format_parse(const char *name, const char *value)
{
    int     code;

    if ((code = name_code(hfrom_format_table, NAME_CODE_FLAG_NONE, value)) < 0)
	msg_fatal("invalid setting: \"%s = %s\"", name, value);
    return (code);
}

/* str_hfrom_format_code - convert code to string */

const char *str_hfrom_format_code(int code)
{
    const char *name;

    if ((name = str_name_code(hfrom_format_table, code)) == 0)
	msg_fatal("invalid header format code: %d", code);
    return (name);
}

#ifdef TEST
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>

#include <vstream.h>
#include <vstring.h>
#include <msg_vstream.h>

#define STR(x) vstring_str(x)

 /*
  * TODO(wietse) make this a proper VSTREAM interface. Instead of temporarily
  * swapping streams, we could temporarily swap the stream's write function.
  */

/* vstream_swap - kludge to capture output for testing */

static void vstream_swap(VSTREAM *one, VSTREAM *two)
{
    VSTREAM save;

    save = *one;
    *one = *two;
    *two = save;
}

jmp_buf test_fatal_jbuf;

#undef msg_fatal

/* test_msg_fatal - does not return, and does not terminate */

void    test_msg_fatal(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_warn(fmt, ap);
    va_end(ap);
    longjmp(test_fatal_jbuf, 1);
}

struct name_test_case {
    const char *label;			/* identifies test case */
    const char *config;			/* configuration under test */
    const char *exp_warning;		/* expected warning or empty */
    const int exp_code;			/* expected code */
};

static struct name_test_case name_test_cases[] = {
    {"hfrom_format_parse good-standard",
	 /* config */ HFROM_FORMAT_NAME_STD,
	 /* warning */ "",
	 /* exp_code */ HFROM_FORMAT_CODE_STD
    },
    {"hfrom_format_parse good-obsolete",
	 /* config */ HFROM_FORMAT_NAME_OBS,
	 /* warning */ "",
	 /* exp_code */ HFROM_FORMAT_CODE_OBS
    },
    {"hfrom_format_parse bad",
	 /* config */ "does-not-exist",
	 /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse bad = does-not-exist\"\n",
	 /* code */ 0,
    },
    {"hfrom_format_parse empty",
	 /* config */ "",
	 /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse empty = \"\n",
	 /* code */ 0,
    },
    0,
};

struct code_test_case {
    const char *label;			/* identifies test case */
    int     code;			/* code under test */
    const char *exp_warning;		/* expected warning or empty */
    const char *exp_name;		/* expected namme */
};

static struct code_test_case code_test_cases[] = {
    {"str_hfrom_format_code good-standard",
	 /* code */ HFROM_FORMAT_CODE_STD,
	 /* warning */ "",
	 /* exp_name */ HFROM_FORMAT_NAME_STD
    },
    {"str_hfrom_format_code good-obsolete",
	 /* code */ HFROM_FORMAT_CODE_OBS,
	 /* warning */ "",
	 /* exp_name */ HFROM_FORMAT_NAME_OBS
    },
    {"str_hfrom_format_code bad",
	 /* config */ 12345,
	 /* warning */ "hfrom_format: warning: invalid header format code: 12345\n",
	 /* exp_name */ 0
    },
    0,
};

int     main(int argc, char **argv)
{
    struct name_test_case *np;
    int     code;
    struct code_test_case *cp;
    const char *name;
    int     pass = 0;
    int     fail = 0;
    int     test_failed;
    VSTRING *msg_buf;
    VSTREAM *memory_stream;

    msg_vstream_init("hfrom_format", VSTREAM_ERR);
    msg_buf = vstring_alloc(100);

    for (np = name_test_cases; np->label != 0; np++) {
	VSTRING_RESET(msg_buf);
	VSTRING_TERMINATE(msg_buf);
	test_failed = 0;
	if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
	    msg_fatal("open memory stream: %m");
	vstream_swap(VSTREAM_ERR, memory_stream);
	if (setjmp(test_fatal_jbuf) == 0)
	    code = hfrom_format_parse(np->label, np->config);
	vstream_swap(memory_stream, VSTREAM_ERR);
	if (vstream_fclose(memory_stream))
	    msg_fatal("close memory stream: %m");
	if (strcmp(STR(msg_buf), np->exp_warning) != 0) {
	    msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
		     np->label, STR(msg_buf), np->exp_warning);
	    test_failed = 1;
	}
	if (*np->exp_warning == 0) {
	    if (code != np->exp_code) {
		msg_warn("test case %s: got code: \"%d\", want: \"%d\"(%s)",
			 np->label, code, np->exp_code,
			 str_hfrom_format_code(np->exp_code));
		test_failed = 1;
	    }
	}
	if (test_failed) {
	    msg_info("%s: FAIL", np->label);
	    fail++;
	} else {
	    msg_info("%s: PASS", np->label);
	    pass++;
	}
    }

    for (cp = code_test_cases; cp->label != 0; cp++) {
	VSTRING_RESET(msg_buf);
	VSTRING_TERMINATE(msg_buf);
	test_failed = 0;
	if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
	    msg_fatal("open memory stream: %m");
	vstream_swap(VSTREAM_ERR, memory_stream);
	if (setjmp(test_fatal_jbuf) == 0)
	    name = str_hfrom_format_code(cp->code);
	vstream_swap(memory_stream, VSTREAM_ERR);
	if (vstream_fclose(memory_stream))
	    msg_fatal("close memory stream: %m");
	if (strcmp(STR(msg_buf), cp->exp_warning) != 0) {
	    msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
		     cp->label, STR(msg_buf), cp->exp_warning);
	    test_failed = 1;
	} else if (*cp->exp_warning == 0) {
	    if (strcmp(name, cp->exp_name)) {
		msg_warn("test case %s: got name: \"%s\", want: \"%s\"",
			 cp->label, name, cp->exp_name);
		test_failed = 1;
	    }
	}
	if (test_failed) {
	    msg_info("%s: FAIL", cp->label);
	    fail++;
	} else {
	    msg_info("%s: PASS", cp->label);
	    pass++;
	}
    }

    msg_info("PASS=%d FAIL=%d", pass, fail);
    vstring_free(msg_buf);
    exit(fail != 0);
}

#endif
