/*++
/* NAME
/*	dsn_util 3
/* SUMMARY
/*	DSN status parsing routines
/* SYNOPSIS
/*	#include <dsn_util.h>
/*
/*	#define DSN_SIZE ...
/*
/*	typedef struct { ... } DSN_BUF;
/*
/*	typedef struct {
/* .in +4
/*		DSN_STAT dsn;		/* RFC 3463 status */
/*		const char *text;	/* Free text */
/* .in -4
/*	} DSN_SPLIT;
/*
/*	DSN_SPLIT *dsn_split(dp, def_dsn, text)
/*	DSN_SPLIT *dp;
/*	const char *def_dsn;
/*	const char *text;
/*
/*	char	*dsn_prepend(def_dsn, text)
/*	const char *def_dsn;
/*	const char *text;
/*
/*	size_t	dsn_valid(text)
/*	const char *text;
/*
/*	void	DSN_UPDATE(dsn_buf, dsn, len)
/*	DSN_BUF	dsn_buf;
/*	const char *dsn;
/*	size_t	len;
/*
/*	const char *DSN_CODE(dsn_buf)
/*	DSN_BUF dsn_buf;
/*
/*	char	*DSN_CLASS(dsn_buf)
/*	DSN_BUF	dsn_buf;
/* DESCRIPTION
/*	The functions in this module manipulate pairs of RFC 3463
/*	status codes and descriptive free text.
/*
/*	dsn_split() splits text into an RFC 3463 status code and
/*	descriptive free text.  When the text does not start with
/*	a status code, the specified default status code is used
/*	instead.  Whitespace before the optional status code or
/*	text is skipped.  dsn_split() returns a copy of the RFC
/*	3463 status code, and returns a pointer to (not copy of)
/*	the remainder of the text.  The result value is the first
/*	argument.
/*
/*	dsn_prepend() prepends the specified default RFC 3463 status
/*	code to the specified text if no status code is present in
/*	the text. This function produces the same result as calling
/*	concatenate() with the results from dsn_split().  The result
/*	should be passed to myfree(). Whitespace before the optional
/*	status code or text is skipped.
/*
/*	dsn_valid() returns the length of the RFC 3463 status code
/*	at the beginning of text, or zero. It does not skip initial
/*	whitespace.
/*
/*	Arguments:
/* .IP def_dsn
/*	Null-terminated default RFC 3463 status code that will be
/*	used when the free text does not start with one.
/* .IP dp
/*	Pointer to storage for copy of DSN status code, and for
/*	pointer to free text.
/* .IP dsn
/*	Null-terminated RFC 3463 status code.
/* .IP text
/*	Null-terminated free text.
/* SEE ALSO
/*	msg(3) diagnostics interface
/* DIAGNOSTICS
/*	Panic: invalid default DSN code.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <stringops.h>

/* Global library. */

#include <dsn_util.h>

/* dsn_valid - check RFC 3463 enhanced status code, return length or zero */

size_t  dsn_valid(const char *text)
{
    const unsigned char *cp = (unsigned char *) text;
    size_t  len;

    /* First portion is one digit followed by dot. */
    if ((cp[0] != '2' && cp[0] != '4' && cp[0] != '5') || cp[1] != '.')
	return (0);

    /* Second portion is 1-3 digits followed by dot. */
    cp += 2;
    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS2
	|| cp[len] != '.')
	return (0);

    /* Last portion is 1-3 digits followed by end-of-string or whitespace. */
    cp += len + 1;
    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS3
	|| (cp[len] != 0 && !ISSPACE(cp[len])))
	return (0);

    return (((char *) cp - text) + len);
}

/* dsn_split - split text into DSN and text */

DSN_SPLIT *dsn_split(DSN_SPLIT *dp, const char *def_dsn, const char *text)
{
    const char *myname = "dsn_split";
    const char *cp = text;
    size_t  len;

    /*
     * Look for an optional RFC 3463 enhanced status code.
     * 
     * XXX If we want to enforce that the first digit of the status code in the
     * text matches the default status code, then pipe_command() needs to be
     * changed. It currently auto-detects the reply code without knowing in
     * advance if the result will start with '4' or '5'.
     */
    while (ISSPACE(*cp))
	cp++;
    if ((len = dsn_valid(cp)) > 0) {
	strncpy(dp->dsn.data, cp, len);
	dp->dsn.data[len] = 0;
	cp += len + 1;
    } else if ((len = dsn_valid(def_dsn)) > 0) {
	strncpy(dp->dsn.data, def_dsn, len);
	dp->dsn.data[len] = 0;
    } else {
	msg_panic("%s: bad default status \"%s\"", myname, def_dsn);
    }

    /*
     * The remainder is free text.
     */
    while (ISSPACE(*cp))
	cp++;
    dp->text = cp;

    return (dp);
}

/* dsn_prepend - prepend optional status to text, result on heap */

char   *dsn_prepend(const char *def_dsn, const char *text)
{
    DSN_SPLIT dp;

    dsn_split(&dp, def_dsn, text);
    return (concatenate(DSN_STATUS(dp.dsn), " ", dp.text, (char *) 0));
}
