/*++
/* NAME
/*	showq_json 8
/* SUMMARY
/*	JSON queue status formatter
/* SYNOPSIS
/*	void	showq_json(
/*	VSTREAM	*showq)
/* DESCRIPTION
/*	This function converts showq(8) daemon output to JSON format.
/* DIAGNOSTICS
/*	Fatal errors: out of memory, malformed showq(8) daemon output.
/* 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
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sysexits.h>
#include <ctype.h>
#include <errno.h>

/* Utility library. */

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

/* Global library. */

#include <mail_proto.h>
#include <mail_queue.h>
#include <mail_date.h>
#include <mail_params.h>

/* Application-specific. */

#include <postqueue.h>

#define STR(x)	vstring_str(x)
#define LEN(x)	VSTRING_LEN(x)

/* json_quote - quote JSON string */

static char *json_quote(VSTRING *result, const char *text)
{
    unsigned char *cp;
    int     ch;

    /*
     * We use short escape sequences for common control characters. Note that
     * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences
     * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC
     * 4627 complaint.
     */
    VSTRING_RESET(result);
    for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) {
	if (UNEXPECTED(ISCNTRL(ch))) {
	    switch (ch) {
	    case '\b':
		VSTRING_ADDCH(result, '\\');
		VSTRING_ADDCH(result, 'b');
		break;
	    case '\f':
		VSTRING_ADDCH(result, '\\');
		VSTRING_ADDCH(result, 'f');
		break;
	    case '\n':
		VSTRING_ADDCH(result, '\\');
		VSTRING_ADDCH(result, 'n');
		break;
	    case '\r':
		VSTRING_ADDCH(result, '\\');
		VSTRING_ADDCH(result, 'r');
		break;
	    case '\t':
		VSTRING_ADDCH(result, '\\');
		VSTRING_ADDCH(result, 't');
		break;
	    default:
		vstring_sprintf_append(result, "\\u%04X", ch);
		break;
	    }
	} else {
	    switch (ch) {
	    case '\\':
	    case '"':
		VSTRING_ADDCH(result, '\\');
		/* FALLTHROUGH */
	    default:
		VSTRING_ADDCH(result, ch);
		break;
	    }
	}
    }
    VSTRING_TERMINATE(result);

    /*
     * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with
     * SMTPUTF8 disabled).
     */
    printable(STR(result), '?');
    return (STR(result));
}

/* json_message - report status for one message */

static void format_json(VSTREAM *showq_stream)
{
    static VSTRING *queue_name = 0;
    static VSTRING *queue_id = 0;
    static VSTRING *addr = 0;
    static VSTRING *why = 0;
    static VSTRING *quote_buf = 0;
    long    arrival_time;
    long    message_size;
    int     showq_status;
    int     rcpt_count = 0;
    int     forced_expire;

    /*
     * One-time initialization.
     */
    if (queue_name == 0) {
	queue_name = vstring_alloc(100);
	queue_id = vstring_alloc(100);
	addr = vstring_alloc(100);
	why = vstring_alloc(100);
	quote_buf = vstring_alloc(100);
    }

    /*
     * Read the message properties and sender address.
     */
    if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
		  | ATTR_FLAG_PRINTABLE,
		  RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
		  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
		  RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
		  RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
		  RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
		  RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
		  ATTR_TYPE_END) != 6)
	msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
    vstream_printf("{");
    vstream_printf("\"queue_name\": \"%s\", ",
		   json_quote(quote_buf, STR(queue_name)));
    vstream_printf("\"queue_id\": \"%s\", ",
		   json_quote(quote_buf, STR(queue_id)));
    vstream_printf("\"arrival_time\": %ld, ", arrival_time);
    vstream_printf("\"message_size\": %ld, ", message_size);
    vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
    vstream_printf("\"sender\": \"%s\", ",
		   json_quote(quote_buf, STR(addr)));

    /*
     * Read zero or more (recipient, reason) pair(s) until attr_scan_more()
     * consumes a terminator. If the showq daemon messes up, don't try to
     * resynchronize.
     */
    vstream_printf("\"recipients\": [");
    for (rcpt_count = 0; (showq_status = attr_scan_more(showq_stream)) > 0; rcpt_count++) {
	if (rcpt_count > 0)
	    vstream_printf(", ");
	vstream_printf("{");
	if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
		      | ATTR_FLAG_PRINTABLE,
		      RECV_ATTR_STR(MAIL_ATTR_RECIP, addr),
		      RECV_ATTR_STR(MAIL_ATTR_WHY, why),
		      ATTR_TYPE_END) != 2)
	    msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
	vstream_printf("\"address\": \"%s\"",
		       json_quote(quote_buf, STR(addr)));
	if (LEN(why) > 0)
	    vstream_printf(", \"delay_reason\": \"%s\"",
			   json_quote(quote_buf, STR(why)));
	vstream_printf("}");
    }
    vstream_printf("]");
    if (showq_status < 0)
	msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
    vstream_printf("}\n");
    if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE)
	msg_fatal_status(EX_IOERR, "output write error: %m");
}

/* showq_json - streaming JSON-format output adapter */

void    showq_json(VSTREAM *showq_stream)
{
    int     showq_status;

    /*
     * Emit zero or more queue file objects until attr_scan_more() consumes a
     * terminator.
     */
    while ((showq_status = attr_scan_more(showq_stream)) > 0
	   && vstream_ferror(VSTREAM_OUT) == 0) {
	format_json(showq_stream);
    }
    if (showq_status < 0)
	msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
}
