/*
** Copyright 1998 - 2018 Double Precision, Inc.  See COPYING for
** distribution information.
*/

/*
*/
#ifndef	rfc2045_h
#define	rfc2045_h

#include	"rfc2045/rfc2045_config.h" /* VPATH build */
#include	"numlib/numlib.h"
#include	<sys/types.h>
#include	<string.h>
#include	<stdio.h>

#ifdef  __cplusplus
extern "C" {
#endif

#if 0
}
#endif

#define RFC2045_MIME_MESSAGE_RFC822 "message/rfc822"
#define RFC2045_MIME_MESSAGE_GLOBAL "message/global"

#define RFC2045_MIME_MESSAGE_DELIVERY_STATUS "message/delivery-status"
#define RFC2045_MIME_MESSAGE_GLOBAL_DELIVERY_STATUS \
	"message/global-delivery-status"

#define RFC2045_MIME_MESSAGE_HEADERS "text/rfc822-headers"
#define RFC2045_MIME_MESSAGE_GLOBAL_HEADERS "message/global-headers"

int rfc2045_message_content_type(const char *);
int rfc2045_delivery_status_content_type(const char *);
int rfc2045_message_headers_content_type(const char *);

#define	RFC2045_ISMIME1(p)	((p) && atoi(p) == 1)
#define	RFC2045_ISMIME1DEF(p)	(!(p) || atoi(p) == 1)

struct rfc2045 {
	struct rfc2045 *parent;
	unsigned pindex;
	struct rfc2045 *next;

	off_t	startpos,	/* At which offset in msg this section starts */
		endpos,		/* Where it ends */
		startbody,	/* Where the body of the msg starts */
		endbody;	/* endpos - trailing CRLF terminator */
	off_t	nlines;		/* Number of lines in message */
	off_t	nbodylines;	/* Number of lines only in the body */
	char *mime_version;
	char *content_type;
	struct rfc2045attr *content_type_attr;	/* Content-Type: attributes */

	char *content_disposition;
	char *boundary;
	struct rfc2045attr *content_disposition_attr;
	char *content_transfer_encoding;
	int content_8bit;		/*
					** Set if content_transfer_encoding is
					** 8bit
					*/
	char *content_id;
	char *content_description;
	char *content_language;
	char *content_md5;
	char *content_base;
	char *content_location;
	struct  rfc2045ac *rfc2045acptr;
	int	has8bitchars;	/* For rewriting */
	int	haslongline;	/* For rewriting */
	unsigned rfcviolation;	/* Boo-boos */

#define	RFC2045_ERR8BITHEADER	1	/* 8 bit characters in headers */
	/* But this is now OK, in UTF8 mode */

#define	RFC2045_ERR8BITCONTENT	2	/* 8 bit contents, but no 8bit
					content-transfer-encoding */
#define	RFC2045_ERR2COMPLEX	4	/* Too many nested contents */
#define RFC2045_ERRBADBOUNDARY	8	/* Overlapping MIME boundaries */

	unsigned numparts;	/* # of parts allocated */

	char	*rw_transfer_encoding;	/* For rewriting */

	/* Use quoted-printable for 8bit content */
#define	RFC2045_RW_7BIT	1

	/*
	** Convert quoted-printable, if the resulting line length is not
	** excessive.
	*/

#define	RFC2045_RW_8BIT	2

	/*
	** Convert quoted printable without checking for maximum resulting
	** line length.
	*/
#define RFC2045_RW_8BIT_ALWAYS 3

	/* Subsections */

	struct rfc2045 *firstpart, *lastpart;

	/* Working area */

	char *workbuf;
	size_t workbufsize;
	size_t workbuflen;
	int	workinheader;
	int	workclosed;
	int	isdummy;
	int	informdata;	/* In a middle of a long form-data part */
	char *header;
	size_t headersize;
	size_t headerlen;

	int	(*decode_func)(struct rfc2045 *, const char *, size_t);
	void	*misc_decode_ptr;
	int	(*udecode_func)(const char *, size_t, void *);
} ;

struct rfc2045attr {
	struct rfc2045attr *next;
	char *name;
	char *value;
	} ;

struct rfc2045 *rfc2045_alloc();
void rfc2045_parse(struct rfc2045 *, const char *, size_t);
void rfc2045_parse_partial(struct rfc2045 *);
void rfc2045_free(struct rfc2045 *);

void rfc2045_mimeinfo(const struct rfc2045 *,
	const char **,
	const char **,
	const char **);

const char *rfc2045_boundary(const struct rfc2045 *);
int rfc2045_isflowed(const struct rfc2045 *);
int rfc2045_isdelsp(const struct rfc2045 *);
char *rfc2045_related_start(const struct rfc2045 *);
const char *rfc2045_content_id(const struct rfc2045 *);
const char *rfc2045_content_description(const struct rfc2045 *);
const char *rfc2045_content_language(const struct rfc2045 *);
const char *rfc2045_content_md5(const struct rfc2045 *);

void rfc2045_mimepos(const struct rfc2045 *, off_t *, off_t *, off_t *,
	off_t *, off_t *);
unsigned rfc2045_mimepartcount(const struct rfc2045 *);

void rfc2045_xdump(struct rfc2045 *);

struct rfc2045id {
	struct rfc2045id *next;
	int idnum;
} ;

void rfc2045_decode(struct rfc2045 *,
		    void (*)(struct rfc2045 *, struct rfc2045id *, void *),
		    void *);

struct rfc2045 *rfc2045_find(struct rfc2045 *, const char *);


/*
** Source of an rfc2045-formatted content (internal)
*/

struct rfc2045src {
	void (*deinit_func)(void *);

	int (*seek_func)(off_t pos, void *);
	ssize_t (*read_func)(char *buf, size_t cnt, void *);

	void *arg;
};
/* Read from a filedesc, returns a malloced buffer */

struct rfc2045src *rfc2045src_init_fd(int fd);

/* Destroy a rfc2045src */

void rfc2045src_deinit(struct rfc2045src *);

/************************/

void rfc2045_cdecode_start(struct rfc2045 *,
	int (*)(const char *, size_t, void *), void *);
int rfc2045_cdecode(struct rfc2045 *, const char *, size_t);
int rfc2045_cdecode_end(struct rfc2045 *);

const char *rfc2045_getdefaultcharset();
void rfc2045_setdefaultcharset(const char *);
struct rfc2045 *rfc2045_fromfd(int);
#define	rfc2045_fromfp(f)	(rfc2045_fromfd(fileno((f))))
struct rfc2045 *rfc2045header_fromfd(int);
#define        rfc2045header_fromfp(f)        (rfc2045header_fromfd(fileno((f))))

extern void rfc2045_error(const char *);


struct  rfc2045ac {
	void (*start_section)(struct rfc2045 *);
	void (*section_contents)(const char *, size_t);
	void (*end_section)();
	} ;

struct rfc2045 *rfc2045_alloc_ac();
int rfc2045_ac_check(struct rfc2045 *, int);
int rfc2045_rewrite(struct rfc2045 *p, struct rfc2045src *src, int fdout_arg,
		    const char *appname);
int rfc2045_rewrite_func(struct rfc2045 *p, struct rfc2045src *src,
			 int (*funcarg)(const char *, int, void *),
			 void *funcargarg,
			 const char *appname);

/* Internal functions */

int rfc2045_try_boundary(struct rfc2045 *, struct rfc2045src *, const char *);
char *rfc2045_mk_boundary(struct rfc2045 *, struct rfc2045src *);
const char *rfc2045_getattr(const struct rfc2045attr *, const char *);
int rfc2045_attrset(struct rfc2045attr **, const char *, const char *);

/* MIME content base/location */

char *rfc2045_content_base(struct rfc2045 *p);
	/* This joins Content-Base: and Content-Location:, as best as I
	** can figure it out.
	*/

char *rfc2045_append_url(const char *, const char *);
	/* Do this with two arbitrary URLs */

/* MISC mime functions */

struct rfc2045 *rfc2045_searchcontenttype(struct rfc2045 *, const char *);
	/* Assume that the "real" message text is the first MIME section here
	** with the given content type.
	*/

int rfc2045_decodemimesection(struct rfc2045src *, /* Message to decode */
			      struct rfc2045 *,	/* MIME section to decode */
			      int (*)(const char *, size_t, void *),
			      /*
			      ** Callback function that receives decoded
			      ** content.
			      */
			      void *	/* 3rd arg to the callback function */
			      );
/*
** Decode a given MIME section.
*/

int rfc2045_decodetextmimesection(struct rfc2045src *, /* Message to decode */
				  struct rfc2045 *, /* MIME section */
				  const char *,	/* Convert to this character set */
				  int *, /* Set to non-0 if MIME section contained chars that could not be converted to the requested charset */
				  int (*)(const char *, size_t, void *),
				  /*
				  ** Callback function that receives decoded
				  ** content.
				  */
				  void * /* 3rd arg to the callback function */
			      );
	/*
	** Like decodemimesction(), except that the text is automatically
	** convert to the specified character set (this function falls back
	** to decodemimesection() if libunicode.a is not available, or if
	** either the specified character set, or the MIME character set
	** is not supported by libunicode.a
	*/


	/*
	** READ HEADERS FROM A MIME SECTION.
	**
	** Call rfc2045header_start() to allocate a structure for the given
	** MIME section.
	**
	** Call rfc2045header_get() to repeatedly get the next header.
	** Function returns < 0 for a failure (out of memory, or something
	** like that).  Function returns 0 for a success.  Example:
	**
	** rfc2045header_get(ptr, &header, &value, 0);
	**
	** If success: check if header is NULL - end of headers, else
	** "header" and "value" will contain the RFC 822 header.
	**
	** Last argument is flags:
	*/

#define RFC2045H_NOLC 1		/* Do not convert header to lowercase */
#define RFC2045H_KEEPNL 2	/* Preserve newlines in the value string
				** of multiline headers.
				*/

struct rfc2045headerinfo *
	rfc2045header_start(struct rfc2045src *,/* Readonly source */
			    struct rfc2045 *	/* MIME section to read */
			    );

int rfc2045header_get(struct rfc2045headerinfo *,
		      char **,	/* Header return */
		      char **,	/* Value return */
		      int);	/* Flags */

void rfc2045header_end(struct rfc2045headerinfo *);


/*
** Generic MIME header parsing code.
**
** header - something like "text/plain; charset=us-ascii; format=flowed".
**
** header_type_cb - callback function, receives the "text/plain" parameter.
**
** header_param_cb - callback function, repeatedly invoked to process the
** additional parameters.  In this example, receives "charset" and "us-ascii".
** Note -t he first parameter will always be in lowercase.
**
** void_arg - passthrough parameter to the callback functions.
*/

int rfc2045_parse_mime_header(const char *header,
			      void (*header_type_cb)(const char *, void *),
			      void (*header_param_cb)(const char *,
						      const char *,
						      void *),
			      void *void_arg);

/*
** The rfc2045_makereply function is used to generate an initial
** reply to a MIME message.  rfc2045_makereply takes the following
** structure:
*/

struct rfc2045_mkreplyinfo {

	struct rfc2045src *src; /* Original message source */

	struct rfc2045 *rfc2045partp;
	/*
	** rfc2045 structure for the message to reply.  This may actually
	** represent a single message/rfc822 section within a larger MIME
	** message digest, in which case we format a reply to this message.
	*/

	void *voidarg;	/* Transparent argument passed to the callback
			** functions.
			*/

	/*
	** The following callback functions are called to generate the reply
	** message.  They must be initialized.
	*/

	void (*write_func)(const char *, size_t, void *);
	/* Called to write out the content of the message */

	void (*writesig_func)(void *);
	/* Called to write out the sender's signature */

	int (*myaddr_func)(const char *, void *);
	/* myaddr_func receives a pointer to an RFC 822 address, and it
	** should return non-zero if the address is the sender's address
	*/

	const char *replymode;
	/*
	** replymode must be initialized to one of the following.  It sets
	** the actual template for the generated response.
	**
	** "forward" - forward original message.
	** "forwardatt" - forward original message as an RFC822 attachment
	** "reply" - a standard reply to the original message's sender
	** "replydsn" - a DSN reply to the original message's sender
	** "feedback" - generate a feedback report (RFC 5965)
	** "replyfeedback" - "feedback" to the sender's address.
	** "replyall" - a "reply to all" response.
	** "replylist" - "reply to mailing list" response.  This is a reply
	** that's addressed to the mailing list the original message was sent
	** to.
	*/

	int replytoenvelope;
	/*
	** If non-zero, the "reply" or "replydsn" message gets addressed to the
	** "Return-Path" or "Errors-To" address, if available.
	*/

	int donotquote;

	/*
	** If donotquote is set, the contents of the original message are not
	** quoted by any of the "reply" modes, and replysalut (below) does not
	** get emitted.
	*/

	int fullmsg;
	/*
	** For replydsn, feedback, replyfeedback, attach the entire message
	** instead of just its headers.
	*/

	const char *replysalut;
	/*
	** This should be set to the salutation to be used for the reply.
	** The following %-formats may appear in this string:
	**
	** %% - an explicit % character
	**
	** %n - a newline character
	**
	** %C - the X-Newsgroup: header from the original message
	**
	** %N - the Newsgroups: header from the original message
	**
	** %i - the Message-ID: header from the original message
	**
	** %f - the original message's sender's address
	**
	** %F - the original message's sender's name
	**
	** %S - the Subject: header from the original message
	**
	** %d - the original message's date, in the local timezone
	**
	** %{...}d - use strftime() to format the original message's date.
	**           A plain %d is equivalent to %{%a, %d %b %Y %H:%M:%S %z}d.
	**
	** Example:  "%F writes:"
	*/

	const char *forwarddescr;
	/*
	** For forwardatt, this is the Content-Description: header,
	** (typically "Forwarded message").
	*/

	/*
	** If not NULL, overrides the Subject: header
	*/

	const char *subject;

	/*
	** When reply mode is 'replydsn', dsnfrom must be set to a valid
	** email address that's specified as the address that's generating
	** the DSN.
	*/
	const char *dsnfrom;

	/*
	** When reply mode is 'replyfeedback', feedbacktype must be set to
	** one of the registered feedback types:
	** "abuse", "fraud", "other", "virus".
	*/
	const char *feedbacktype;

	/*
	** Feedback report headers.
	**
	** NOTE: rfc2045_makereply() automatically inserts the
	** Feedback-Type: (from feedbacktype), User-Agent:, Version:, and
	** Arrival-Date: headers.
	**
	** This is an array of alternating header name and header value
	** strings. The header name string does not contain a colon,
	** rfc2045_makereply supplies one. And, basically, generates
	** "name: value" from this list.
	**
	** For convenience-sake, the capitalization of the headers get
	** adjusted to match the convention in RFC 5965.
	**
	** The list, which must contain an even number of strings, is terminated
	** by a NULL pointer.
	*/
	const char * const *feedbackheaders;

	/*
	** Set the reply/fwd MIME headers. If this is a NULL pointer,
	** write_func() receives ``Content-Type: text/plain; format=flowed;
	** delsp=yes; charset="charset" '' with the charset specified below,
	** and "Content-Transfer-Encoding: 8bit".
	**
	** If this is not a NULL pointer, the effect of
	** this function should be invocation of write_func() to perform the
	** analogous purpose.
	**
	** The output of content_set_charset() should be consistent with the
	** contents of the charset field.
	*/

	void (*content_set_charset)(void *);

	/*
	** Set the reply/fwd content.
	**
	** This function gets called at the point where the additional contents
	** of the reply/fwd should go.
	**
	** If this is not a NULL pointer, the effect of this function should
	** be invocation of write_func() with the additional contents of the
	** reply/fwd. The added content should be consistent with the
	** charset field.
	**
	** Note -- this content is likely to end up in a multipart MIME
	** message, as such it should not contain any lines that look like
	** MIME boundaries.
	*/

	void (*content_specify)(void *);

	const char *mailinglists;
	/*
	** This should be set to a whitespace-delimited list of mailing list
	** RFC 822 addresses that the respondent is subscribed to.  It is used
	** to figure out which mailing list the original message was sent to
	** (all addresses in the original message are compared against this
	** list).  In the event that we can't find a mailing list address on
	** the original message, "replylist" will fall back to "replyall".
	*/

	const char *charset;
	/* The respondent's local charset */

	const char *forwardsep;
	/* This is used instead of replysalut for forwards. */
} ;

int rfc2045_makereply(struct rfc2045_mkreplyinfo *);

/********** Search message content **********/

/*
** Callback passed rfc2045_decodemsgtoutf8()
*/

struct rfc2045_decodemsgtoutf8_cb {

	int flags; /* Optional flags, see below */

	/* Define a non-null function pointer. It gets the name of a header,
	** and the raw, unformatted, header contents.
	** If returns non-0, the header gets converted and sent to output.
	** If null, all headers are sent
	*/

	int (*headerfilter_func)(const char *name, const char *raw, void *arg);

	/* The output function */
	int (*output_func)(const char *data, size_t cnt, void *arg);

	/* If not null, gets invoked after decoding a single header */
	int (*headerdone_func)(const char *headername, void *arg);

	void *arg; /* Passthrough arg to _funcs */
};

#define RFC2045_DECODEMSG_NOBODY 0x01
/* Do not decode MIME content, headers only */

#define RFC2045_DECODEMSG_NOHEADERS 0x02
/*
** Do not decode MIME headers, only body. This is the same as using a
** headerfilter_func that always returns 0
*/

#define RFC2045_DECODEMSG_NOHEADERNAME 0x04
/*
** Do not prepend name: to converted header content.
*/


#define RFC2045_DECODEMSG_NOATTACHHEADERS 0x08
/*
** Do not decode MIME headers of attachments. Decode only the message's
** main headers.
*/

/*
** Convert a message into a utf8 bytestream. The output produced by this
** function is a catentation of decoded header and text content data, converted
** to utf8.
**
** This is fed into an output function. The output function takes a single
** octet, and returns 0 if the octet was processed, or a negative value if
** the output was aborted.
*/

int rfc2045_decodemsgtoutf8(struct rfc2045src *src, /* The message */
			    struct rfc2045 *p, /* The parsed message */

			    /* The callback */
			    struct rfc2045_decodemsgtoutf8_cb *callback);


/********** Decode RFC 2231 attributes ***********/

/*
** rfc2231_decodeType() decodes an RFC 2231-encoded Content-Type: header
** attribute, and rfc2231_decodeDisposition() decodes the attribute in the
** Content-Disposition: header.
**
** chsetPtr, langPtr, and textPtr should point to a char ptr.  These
** functions automatically allocate the memory, the caller's responsible for
** freeing it.  A NULL argument may be provided if the corresponding
** information is not wanted.
*/

int rfc2231_decodeType(struct rfc2045 *rfc, const char *name,
		       char **chsetPtr,
		       char **langPtr,
		       char **textPtr);

int rfc2231_decodeDisposition(struct rfc2045 *rfc, const char *name,
			      char **chsetPtr,
			      char **langPtr,
			      char **textPtr);

/*
** The following two functions convert the decoded string to the local
** charset via unicodelib.  textPtr cannot be null, this time, because this
** is the only return value.   A NULL myChset is an alias for the default
** charset.
*/

int rfc2231_udecodeType(struct rfc2045 *rfc, const char *name,
			const char *myChset,
			char **textPtr);

int rfc2231_udecodeDisposition(struct rfc2045 *rfc, const char *name,
			       const char *myChset,
			       char **textPtr);

/*
** Build an RFC 2231-encoded name*=value.
**
** name, value, charset, language: see RFC 2231.
**
** (*cb_func) gets invoked 1 or more time, receives a "name=value" pair
** each time.
**
** cb_func must return 0; a non-0 return terminates rfc2231_attrCreate, which
** passes through the return code.
**
*/
int rfc2231_attrCreate(const char *name, const char *value,
		       const char *charset,
		       const char *language,
		       int (*cb_func)(const char *param,
				      const char *value,
				      void *void_arg),
		       void *cb_arg);

/** NON-PUBLIC DATA **/

struct rfc2231param {
	struct rfc2231param *next;

	int paramnum;
	int encoded;

	const char *value;
};

void rfc2231_paramDestroy(struct rfc2231param *paramList);
int rfc2231_buildAttrList(struct rfc2231param **paramList,
			  const char *name,

			  const char *attrName,
			  const char *attrValue);

void rfc2231_paramDecode(struct rfc2231param *paramList,
			 char *charsetPtr,
			 char *langPtr,
			 char *textPtr,
			 int *charsetLen,
			 int *langLen,
			 int *textLen);

/*
** Encode an E-mail address as utf-8 address type specified in RFC 6533.
** The e-mail address parameter must be encoded in UTF-8.
**
** The E-mail address is encoded as "rfc822" address type if it has only
** ASCII characters, or if use_rfc822 is set to non0.
**
** A malloc-ed address gets returned.
*/

char *rfc6533_encode(const char *address, int use_rfc822);

/*
** Decode a utf-8 or an rfc-822 address type. Returns a malloc-ed buffer,
** or NULL if the address cannot be decoded.
**
** Assumes valid UTF-8 coding, and does not verify it.
**
** Does verify, for both rfc-822 and utf-8 formats, that the returned address
** does not contain control characters.
*/

char *rfc6533_decode(const char *address);

#if 0
{
#endif

#ifdef  __cplusplus
}
#endif

#endif
