/*
** Copyright 1998 - 2026 S. Varshavchik.  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>

#include <vector>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <optional>
#include <variant>
#include <charconv>
#include <thread>
#include <mutex>
#include <streambuf>
#include <optional>
#include <condition_variable>
#include <tuple>
#include <courier-unicode.h>
#include "rfc822/rfc822.h"
#include "rfc822/rfc2047.h"
#include "rfc2045/rfc2045charset.h"
#include "rfc2045/rfc2045.h"

#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"

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

#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 */

namespace rfc2045 {

	extern std::string default_charset;

	class entity;
	class entity_info;
	class entity_parse_meta;
	class entity_parser_base;
	template<bool crlf> class entity_parser;
	class headers_base;
	struct reply;
	enum class replymode_t;

	// Possible Content-Transfer-Encoding values
	enum class cte { error=0, sevenbit='7', eightbit='8', qp='Q',
			 base64='B'};

	// Look at the two characters of Content-Transfer-Encoding

	inline cte to_cte(char ch1, char ch2)
	{
		switch (ch1) {
		case '7':
			return cte::sevenbit;
		case '8':
			return cte::eightbit;
		case 'q':
		case 'Q':
			return cte::qp;
		case 'b':
		case 'B':
			switch (ch2) {
			case 'i':
			case 'I':
				// Treat "binary" as 8bit
				return cte::eightbit;
			}
			return cte::base64;
		}

		return cte::error;
	}

	// Content-Transfer-Encoding name
	inline const char *to_cte(cte c)
	{
		switch (c) {
		case cte::error:
			break;
		case cte::sevenbit:
			return "7bit";
		case cte::eightbit:
			return "8bit";
		case cte::qp:
			return "quoted-printable";
		case cte::base64:
			return "base64";
		}
		return "error";
	}

	// What kind of Content-Transfer-Encoding: conversion is requested

	enum class convert {

		// Only add missing Content-Type and
		// Content-Transfer-Encoding headers
		standardize='0',

		// Replace 8-bit content with quoted-printable, and
		// replace 7-bit content with haslongline set with
		// quoted-printable
		sevenbit='7',

		// Replace quoted-printable with 8bit if the resulting
		// line length is not haslongline

		eightbit='8',

		// Always replace quoted-printable with 8bit, even if the
		// end result produces long lines (in which case it's
		// converted to 8bit or 7bit, as appropriate)
		eightbit_always='A'
	};

	std::string append_url(std::string_view, std::string_view);

	bool message_content_type(std::string_view);
	bool delivery_status_content_type(std::string_view);
	bool message_headers_content_type(std::string_view);
} ;

namespace rfc822 {

	// Returns:
	//
	// - "base64", "quoted-printable", "7bit" or "8bit"
	//
	// - a bool flag indicating whether or not a raw binary was detected

	std::tuple<const char *, bool>
	libmail_encode_autodetect(
		std::streambuf &,
		bool use7bit
	);
}

namespace rfc6533 {

	// RFC 6533 encoding format

	enum class format {
		utf_8,		// Native UTF-8

		xtext,		// utf8-add-xtext

		unitext,	// utf-8-addr-unitext

		rfc822		// RFC-822, and convert domain to ACE, too
	};

	// Use RFC 6533 to encode a UTF-8 address.
	//
	// The E-mail address is encoded as "rfc822" address type if it has only
	// ASCII characters and format is utf_8, otherwise the format parameter
	// specifies the "utf-8" encoding format.

	std::string encode(std::string_view address, format f);

	// Overload for a u32string_view
	std::string encode(std::u32string_view address, format f);

	// Decode a utf-8 or an rfc-822 address type.
	//
	// 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.

	std::string decode(std::string_view address);
}

/*
  Build an RFC 2231-encoded name*=value.

  name, value, charset, language: see RFC 2231.

  The callback gets called 1 or more time, with two const char *parameters,

  NOTE: the sum total of name+charset+language cannot exceed 60 characters.

  RFC2231 encoding is not used if:

  - the sum total of octets in name and value does not exceed 75 characters,

  - the value does not contain 8 bit or special characters.

  - language is an empty string.

  - charset is either empty, "us-ascii", or "utf-8".

  If so the callback gets invoked once, with the name and address passed in,
  as is. Otherwise RFC 2231 encoding is used and the callback gets invoked
  one or more times.

  rfc2231::attr_encode returns the value returned from the callback (possibly
  void). Additionally, if the callback returns an integral value: a non-zero
  return from the callback aborts encoding (if RFC 2231 encoding is in use)
  and returns the non-0 returned value, and a 0 value is returned if all
  calls to the callback returned 0.
*/

namespace rfc2231 {
#if 0
}
#endif

inline bool do_encode(char c)
{
	switch (c) {
	case '(':
	case ')':
	case '\'':
	case '"':
	case '\\':
	case '%':
	case ':':
	case ';':
	case '=':
		return true;
	}

	return c <= ' ' || c >= 127;
}

template<typename C>
auto attr_encode_simple(const char *name,
			std::string_view value,
			C && callback)
	-> decltype(std::declval<C &&>()(std::declval<const char *>(),
					 std::declval<const char *>()));

template<typename C, bool integral_return=std::is_integral_v<decltype(
	std::declval<C &&>()(std::declval<const char *>(),
			     std::declval<const char *>()))>>
auto attr_encode(std::string_view name,
		 std::string_view value,
		 std::string_view charset,
		 std::string_view language,
		 C &&callback)
{
	if (name.size() > 60 ||
	    charset.size() > 60 ||
	    language.size() > 60 ||
	    name.size()+charset.size()+language.size() > 60)
	{
		// Sanity check

		errno=EINVAL;
		if constexpr(integral_return)
		{
			return -1;
		}
		else
		{
			return;
		}
	}

	char name_buf[name.size()+1];

	// header::parameters_t already have lowercase keys, this is for the
	// benefit of callers who call this directly.
	*std::transform(name.begin(),
			name.end(),
			name_buf,
			[]
			(char c)
			{
				if (c >= 'A' && c <= 'Z')
				{
					c += 'a'-'A';
				}
				return c;
			})=0;

	std::string_view name_buf_s{name_buf};

	// boundary delimiter strings can be long, make sure we don't go
	// off the reservation.
	if (name_buf_s == "charset" || name_buf_s == "boundary")
		return attr_encode_simple(
			name_buf,
			value,
			std::forward<C>(callback)
		);

	if (name.size() + value.size() <= 75 &&
	    language.empty() &&
	    std::find_if(value.begin(), value.end(), do_encode) ==
	    value.end())
	{
		if (charset.size() < 20)
		{
			char buf[20];

			*std::transform(charset.begin(),
					charset.end(),
					buf,
					[]
					(char c)
					{
						if (c >= 'A' && c <= 'Z')
						{
							c += 'a'-'A';
						}
						return c;
					})=0;

			std::string_view sbuf{buf};

			if (sbuf.empty() ||
			    sbuf == unicode::utf_8 ||
			    sbuf == "us-ascii")
			{
				return attr_encode_simple(
					name_buf,
					value,
					std::forward<C>(callback)
				);
			}
		}
	}

	size_t n=0;

	auto vb=value.begin();
	auto ve=value.end();

	// name+charset+language is less than 60 chars

	char buf[100];

	do
	{
		auto b=std::begin(buf);
		auto e=b+70;

		b=std::copy(name.begin(), name.end(), b);
		*b++='*';

		b=std::to_chars(b, e, n).ptr;

		*b++='*';
		*b++=0;
		char *value_begin=b;

		if (n == 0)
		{
			b=std::copy(charset.begin(), charset.end(), b);
			*b++='\'';
			b=std::copy(language.begin(), language.end(), b);
			*b++='\'';
		}

		++n;

		while (b < e && vb < ve)
		{
			if (do_encode(*vb))
			{
				*b++='%';
				*b++="0123456789ABCDEF"[ (*vb >> 4) & 15];
				*b++="0123456789ABCDEF"[ *vb & 15];
			}
			else
				*b++=*vb;
			++vb;
		}

		*b=0;

		if constexpr(integral_return)
		{
			auto v=callback(buf, value_begin);

			if (v != 0)
				return v;
		}
		else
		{
			callback(buf, value_begin);
		}
	} while (vb < ve);

	if constexpr (integral_return)
	{
		return 0;
	}
}

template<typename C>
auto attr_encode_simple(const char *name,
			std::string_view value,
			C && callback)
	-> decltype(std::declval<C &&>()(std::declval<const char *>(),
					 std::declval<const char *>()))
{
	char value_buf[value.size()+3];

	bool quotes_needed=std::find_if(
		value.begin(),
		value.end(),
		[]
		(char c)
		{
			static const char specials[]=
				RFC822_SPECIALS;

			return std::find(
				std::begin(specials),
				std::end(specials),
				c) !=
				std::end(specials);
		}) != value.end();

	char *p=value_buf;

	if (quotes_needed)
		*p++='"';
	p=std::copy(value.data(), value.data()+
		    value.size(), p);

	if (quotes_needed)
		*p++='"';
	*p=0;

	return callback(name, value_buf);
}

// A parameter of a structured MIME header.
struct header_parameter_value {

	// Original ordering of parameter values in the header.
	size_t index{0};

	// If RFC 2231 was used to encode the character set
	std::string charset{unicode::utf_8};

	// If RFC 2231 was used to encode the language
	std::string language;

	// Finally, the value.
	std::string value;

	template<typename V>
	header_parameter_value(V && v)
		: value{std::forward<V>(v)}
	{
	}

	template<typename C, typename L, typename V>
	header_parameter_value(size_t index,
			       C && charset,
			       L && language,
			       V && value)
		: index{index},
		  charset{std::forward<C>(charset)},
		  language{std::forward<L>(language)},
		  value{std::forward<V>(value)}
	{
	}

	std::string value_in_charset() const;
	std::string value_in_charset(std::string_view) const;

	bool operator==(const header_parameter_value &o) const
	{
		return charset == o.charset &&
			language == o.language &&
			value == o.value;
	}
};

// A structured MIME header, a value, and parameters, optionally encoded
// using RFC 2231
struct header {
	std::string value;
	typedef std::unordered_map<std::string,
				   header_parameter_value> parameters_t;
	parameters_t parameters;

	// Parses a folded header value.
	header(const std::string_view &, bool do_parse_rfc2231);

	void lowercase_value(const char *name);

	bool format_flowed() const
	{
		auto iter=parameters.find("format");

		return iter != parameters.end() &&
			iter->second.value == "flowed";
	}

	bool delsp_yes() const
	{
		auto iter=parameters.find("delsp");

		return iter != parameters.end() &&
			std::string_view{iter->second.value}.substr(
				0, 1
			) == "y";
	}
};

// Helper for parsing parameters. Handles RFC 2231 decoding. Instantiate,
// call (name, value) for each parsed name+value tuple, then >> into
// the final parameters_t.

struct parameter_parser {

	size_t index=0;

	// Value of a structured header parameter, parsed according to RFC 2231.
	// This is preliminary parsing, once it's parsed this'll get reassembled
	// into a single parsed string, representing the value.

	struct rfc2231_parsed_parameters {
		size_t index=0;
		std::string charset{unicode::utf_8};
		std::string language;
		std::map<std::optional<int>, std::string> values;
	};

	// First, parse the parameters into a temporary structure.
	std::unordered_map<std::string,
			   rfc2231_parsed_parameters> parsed_parameters;

	void operator()(std::string name, std::string value);
	void operator>>(header::parameters_t &);
};

#if 0
{
#endif
}

/*
  C++ MIME parser.

  Summary:

  rfc822::fdstreambuf fdstream;

  std::istream input_stream{&fdstream};

  std::istreambuf_iterator<char) b{input_stream};
  std::istreambuf_iterator<char> e{};

  rfc2045::entity::line_iter<false>::iter parser{b, e}

  rfc2045::entity entity;

  entity.parse(parser);

  The iter class's constructor takes a reference to an iterator to
  the beginning of an input sequence that defines a MIME message and an ending
  iterator value.

  Typically, rfc822::fdstreambuf provides a std::streambuf for a file
  descriptor, it is fed to a std::istream, and then used to create
  std::istreambuf_iterators that construct the parser.

  The iterators must be passed by reference. parse() updates them, and normally
  the beginning iterator gets advanced to the ending iterator, unless a fatal
  parsing error occurred.

  The iter class is a template, and the template parameters should get deduced
  from the constructor's parameters. Older C++17 compilers may not fully
  implement template parameter deductions, so they must be specified. iter
  has two template parameters, the beginning and the ending iterator type (they
  don't need to be the same).

  line_iter's template parameter must be always explicit, the "false" value
  specifies that NL gets recognized as the newline sequence, "true" value
  specifies that CRNL gets recognized as the newline sequence of the MIME
  message.

  parse() reads the sequence that defines the MIME message and fills in the
  class's members that specify where the MIME entity's header and body
  starts and ends (header always starts at 0), and the number of lines in the
  header and the body.

  A MIME entity that contains other entities fills in the subentities class
  member with an entity for each sub-entity (whose headers will not obviously
  start at position 0 in the input sequence).

  iter.longquotedlinesize=500;

  quoted-printable-encoded MIME entities may split logical lines into
  multiple physical lines. An "=" at the end of the line in a quoted-printable
  MIME entity indicates that the logical lines continues on the next line.

  longquotedlinesize sets the maximum number of characters in a quoted-printable
  encoded MIME entity that forms a single encoded line.
  that continues into multiple physical lines.

  if (entity.errors & RFC2045_ERRLONGQUOTEDPRINTABLE)

  A MIME entity has this flag set if it has a quoted-printable content that'
  exceeds longquotedlinesize characters in size. Long quoted lines have no
  effect on general MIME parsing, this is merely tracked and serves as an
  indication that the MIME entity should not be reencoded into 7bit or 8bit
  encoding because this will result in very long lines.

  if (entity.has8bitheader)

  The has8bitheader flag is set if the MIME entity's headers contain characters
  outside of the 7bit US-ASCII range. They are presumed to use UTF-8.

  if (entity.has8bitbody)

  The has8bitbody flag is set if the MIME entity's body contains characters
  outside of the 7-bit US-ASCII range.

  if (entity.has8bitcontentchar)

  The has8bitbody flag is set if the MIME entity's body contains characters
  outside of the 7-bit US-ASCII range. Additionally it is set if the MIME
  entity uses quoted-printable encoding and at least one encoded character
  outside of the US-ASCII 7 bit range.

  Note that subentities exist in the body portion of their mutlipart (and
  message) MIME entities. A subentity with has8bitheader or has8bitbody
  flag set results in its parent entity having has8bitbody flag set too,
  and has8bitcontentchar set for a subentity results in the parent entity's
  has8bitcontentchar set, too.

  Other rfc2045::entity members:

  startpos - character offset in the input sequence that defines the
  starting position.

  startbody, endbody - character offsets in the input sequence that define the
  starting position, and one past the ending position, of the MIME entity's
  body section. A blank line that separates a MIME entity header from its
  body is considered to be a part of the header, and startbody points to the
  start of the next line.

  nlines, nbodylines - number of lines in the header and the body portion of
  the MIME entity. Ntoe - a MIME entity that does not have a trailing newline
  seuqnece ends with a partial line. This line is included in the count.

  errors - a bitmask of events that occured while parsing. Not all bitmask flags
  comprise an error event, and RFC2045_ERRFATAL is set when the error is fatal
  and parsing is aborted. parse() may return before the starting iterator
  becomes equal to the ending iterator.

  mime1 - indicates the presence of a "MIME-Version: 1.0" header, an explicit
  one, or an implicit one for a subentity of a MIME 1.0 entity.

  content_type, content_type_charset, content_transfer_encoding - corresponding
  parts of the header, converted to lowercase.

  Header parsing
  ==============

  rfc2045::entity::line_iter<false>::headers headers{entity, input_stream};

  rfc2045::entity::line_iter<false>::headers headers{input_stream};

  headers.name_lc=true;
  headers.keep_eol=false;
  headers.max=16384;

  while (!headers.finished())
  {
	...
  }

  std::string_view current_header{headers.current_header()};

  const auto &[name, content] = headers.name_content();

  bool flag=headers.next();

  The headers class is actually a template, whose template parameter gets
  deduced from the constructor's last parameter. The template implements
  reading one header at a time, of the given MIME entity's headers (two
  arguments passed to the constructor) or the current position in the input
  stream (one argument passed to the constructor. It's constructed
  from a MIME entity object, and a std::streambuf object or another object
  with the following methods that are compatible with std::streambuf's:

  - pubseekpos - seek to the given position of the underlying message, the
  constructor retrieves the passed in MIME entity's headers' starting position
  and seeks to it.

  - sgetc - read a character without advancing the input position

  - sbumpc - read a cahracter and consume it, advancing the input to the next
  position.

  NOTE: the input stream object must not go out of scope and get destroyed
  as long as the parser template instance is in use.

  The constructor positions the underlying at the start of the entity's headers.
  current_header() returns a single std::string_view containing the current
  header (initially the first header in the MIME entity).

  name_content() parses out the current header's name and content (trimming off
  the leading and trailing whitespace from the content).

  next() advances to the next MIME entity's header. Subsequent calls to
  current_header() or name_content() reference the next header.

  The headers template contains the following fields:

  - name_lc (true by default): whether the header's name, as returned by
  by current_header() and name_content(), is converted to lowercase.

  - keep_eol (false by default): setting this field to true has the effect
  of not removing newlines from the header's contents. name_content() still
  trims off all leading and trailing whitespace, but current_header() returns
  the entire header, as is, including the trailing whitespace.

  - max (16384 by default): each read header is truncated to the given
  maximum size.

  These fields must be set before the first call to current_header(),
  name_content(), and next().

  next() returns false if there are no more headers in the MIME entity. Note
  that the empty line that separates the MIME entity's headers from its body
  is considered to be a header, and is included in the headers returned here.
  finished() returns true after next() returns false.

  NOTE: headers longer than max are silently truncated.

  rfc2045::entity::line_iter<false>::headers headers{input_stream};

  Passing in only an input stream results in reading the headers from that
  stream, until an empty line gets read, indicating end of headers. An optional
  size_t parameter to the constructor sets an explicit upper limit on the
  number of characters left.

  The underlying stream object must implement pubseekoff() in addition
  to pubseekpos().

  DECODING MIME ENTITIES
  ======================

  rfc2045::mime_decoder decoder{out, input_stream, "utf-8"};

  rfc2045::mime_unicode_decoder{out, input_stream};

  decoder.decode_header=true;
  decoder.decode_body=true;
  decoder.add_eol=false;
  decoder.header_name_lc=true;
  decoder.header_name_suppress=false;
  decoder.decode_subentities=true;

  decoder.headerfilter=[](std::string_view name, std::string_view content)
          -> bool
      {
          return true;
      };
  decoder.headerdone=[](std::string_view name)
      {
      };
  decoder.decode<false>(entity);

  The decoder classes are actually templates, whose template parameters should
  get deduced from the constructor's parameters. The second parameter is
  a std::streambuf object or another object that implements pubseekpos,
  sgetc, sbumpc, and sgetn function like std::streambuf does. This object
  must be the same object that was used to parse a MIME entity to be decoded.

  mime_decoder's first constructor parameter is a callable object,
  with a const char * and a size_t parameters, which gets repeatedly invoked
  to produce the decoded content. mime_unicode_decoder's first constructor
  parameter is a callable object with a const char32_t * and a size_t
  parameter.

  The callable object may be passed in by reference, which saves
  a reference to the callable object (which then must remain in scope and
  not get destroyed while the decoder object is in use), or by value, which
  saves a copy of the value in the decoder object.

  Finally the third parameter, to mime_decoder, is a character set.
  A plain text MIME entity's decoded contents get transcoded to the specified
  character set. This is an optional parameter, no transcoding takes place
  when it's not specified.

  The first parameter can be passed by reference or by value. If passed by
  value it's copied and stored in the decoder object.

  The second parameter always gets passed by reference,

  The decode() method takes the parsed MIME entity and its decoded contents,
  and writes the following to the output iterator:

  - the decode_header class member (default: true): decode the MIME entity's
  headers. Folded headers are unfolded, each line consists of the header
  name converted to lowercase, followed by its contents. RFC 2047-encoded
  words are decoded, and punycoded domain names are converted, to the character
  set specified by the third parameter.

  - the decode_body class member (default: true): extract the MIME entity's
  contents (except for a multipart or a message MIME content type). base64
  and quoted-printable encoding gets decoded. text content gets converted to
  the character set specified by the optional third parameter to the
  constructor.

  - add_eol (default: false) - generate a newline after the decoded body
  (effective only when decode_body is set). Multipart entities may not
  terminate with a newline sequence, as per MIME specification, this
  ensures that the decoded body always ends with a newline (note, the
  newline gets added even if the decoded body has its own terminating newline).

  - header_name_lc (default: true) - convert each header's name to lowercase
  (effetive only when decode_header is set).

  - header_name_suppress (default: false) - used only if decode_header is
  true. If set to true each header's name is not included in the content
  passed to the output function, only the header's value.

  - decode_subentities: recursively process decode_header and decode_bopdy
  for all MIME subentities

  - headerfilter: an optional header filter. If set, the filter is called for
  each header, and receives the header's name and value as parameter. Returning
  true passes the header to the output. Returning false omits the header from
  the output entirely. If not set all headers get included in the output.

  - headerdone: an optional closure, it's called after outputing each header,
  and receives the header's name as parameter.

  decode()'s template parameter defaults to false and sets the newline sequence
  that the MIME object was parsed with (false, default, is "\n", and true
  is "\r\n").

  decoder.decode<false, false>(entity);

  if (decoder.decoding_error)
  {
     // ...
  }

  decode()'s optional second template parameter defaults to true. If the
  decoded MIME content cannot be converted to the specified character set,
  the non-converted portion gets deleted and an error message gets appended
  to the decoded text. Setting the optional second tempalte parameter to false
  omits the error message. The decoding_error flag can be checked to determine
  if this happened.

  REWRITING MIME ENTITIES
  =======================

  #include <rfc2045/encode.h>

  bool flag=entity.autoconvert_check(rfc2045::convert::standardize);

  This checks if the MIME entity should be rewritten. This must be called
  before autoconvert(), and autoconvert_check() may make changes to the MIME
  object. Specifically it collapses the structure of multipart/signed to
  ensure that it remains unchanged. autoconvert_check() returns false if
  the MIME object does not need rewriting, and true if autoconvert() will
  make changes.

  rfc2045::entity::autoconvert_meta metadata;

  metadata.appid="courier";

  rfc2045::entity::line_iter<false>::autoconvert(
      entity,
      []
      (const char *ptr, size_t l)
      {
         // ...
      },
      input_streambuf,
      metadata);

  autoconvert() proceeds and rewrites the MIME entity, accordingly. The
  second parameter is a callable object that gets called repeatedly with
  the content of the rewritten entity, in chunks. autoconvert() returns the
  final value of the callable object if it was passed by value. autoconvert()
  returns void if the allable object is passed by reference.

  The third parameter is a reference to a std::streambuf or another object
  that implements the same functions as std::streambuf. This streambuf
  represents the contents of the parsed mime entity.

  The last parameter is optional. It is an object with the following fields:

  - appid: this is the name of the application that's calling autoconvert(),
    this is recorded in the X-Mime-Autoconverted headers, if any are added.

  - rwheader(): the default implementation does nothing, and returns its
    header parameter value. A subclass can override and implement a function
    that gets called for every logical header. The function may return a
    different header, and this effectively replaces the original header in
    the message.

  ENUMERATING MIME STRUCTURE
  ==========================

  entity.enumerate(
	  []
	  (const std::string &id, const rfc2045::entity &e)
	  {
	  }
  );

  This enumerates the MIME structure of this MIME entity and all of its
  subentities (if any). The passed-in callable object gets repeatedly invoked
  with two parameters: that gives the identifier of the MIME entity (with
  respect to the entity whose enumerate() method is called) that's given in
  the second parameter.

  The current implementation uses a comma-separated list of integers, a vector.
  A vector with a single value 1 identifies the MIME entity being enumerated.
  If it's a multipart subentity then each one of them is identified by the
  second value in the vector, starting with 1 for the first subentity, 2 for
  the second, and so on. If the subentities have their own subentities they
  get enumerated by additional values, and so on.

  PARSING DELIVERY STATUS NOTIFICATIONS
  =====================================

  bool flag=entity.dsn(src,
		       []
		       (const rfc2045::entity::dsn &dsn)
		       {
		           // ...
		       });

  dsn() parses a MIME entity containing a delivery status notification.
  The first parameter is a std::streambuf or another object that implements
  a compatible API that contains the parsed MIME entity.

  The second parameter is a callable object that gets repeatedly invoked
  with a rfc2045::entity::dsn parameter, which contains the following

  - std::string_view action - the notification "action" value, in lowercase
  - std::string_View original_recipient - the notification "original recipient"
    address.
  - std::string_view final_recipient - the notification "final recipient"
    address.

  The following applies to both the original_recipient and final_recipient
  values:

  * if not specified in the delivery status notification, the address value
  is an empty string

  * the address is converted to the rfc2045_getdefaultcharset(). An address
  given directly in UTF-8 is converted to the character set, if it cannot
  be displayed in this character set the non-displayable characters are
  dumped in hexadecimal. An address given in ASCII-compatible encoding
  is converted to rfc2045_getdefaultcharset(), if possible. Otherwise it
  is left in its ACE encoding. Using rfc2045_setdefaultcharset("utf-8") (or
  running in a native UTF-8 loale) produes the best results.

*/

#define RFC2045_ERR8BITINQP		0x0010
#define RFC2045_ERRBADHEXINQP		0x0020
#define RFC2045_ERRWRONGBOUNDARY	0x0040
#define RFC2045_ERRLONGUNFOLDEDHEADER	0x0080
#define RFC2045_ERRUNKNOWNTE		0x0100
#define RFC2045_ERRINVALIDBASE64	0x0200
#define RFC2045_ERRLONGQUOTEDPRINTABLE  0x0400
#define RFC2045_ERRDUPLICATECONTENT	0x0800
#define RFC2045_ERRFATAL		0x8000

class rfc2045::entity_info {
 public:
	// Sub-entities of multipart MIME entities
	//
	// message MIME entities will have a single subentity, the MIME
	// message itself, unless it uses a quoted-printable MIME encoding

	std::vector<rfc2045::entity> subentities;

protected:
	// Parent entity. rfc2045::entity will update this, nullptr for top lvl
	rfc2045::entity *parent_entity=nullptr;
public:
	rfc2045::entity *get_parent_entity()
	{
		return parent_entity;
	}

	const rfc2045::entity *get_parent_entity() const
	{
		return parent_entity;
	}

	size_t	startpos=0,	/* Position where this entity's header begins */
		startbody=0,	/* Where the body of the entity starts */
		endbody=0;	/* Ending position */
	size_t	nlines=0;	/* Number of lines in message */
	size_t	nbodylines=0;	/* Number of lines only in the body */
	bool no_terminating_nl{false}; /* Ditto */

	size_t rfc822_size(bool crlf=true) const
	{
		size_t s=endbody-startpos;

		if (crlf)
			s += nlines;
		if (no_terminating_nl && crlf && s)
			--s;
		return s;
	}

	size_t rfc822_body_size(bool crlf=true) const
	{
		size_t s=endbody-startbody;

		if (crlf)
			s += nbodylines;

		if (no_terminating_nl && crlf && s)
			--s;
		return s;
	}

	size_t rfc822_bodylines() const
	{
		size_t s=nbodylines;

		if (no_terminating_nl && s)
			--s;
		return s;
	}

	typedef unsigned errors_t;

	struct parsing_error {
		errors_t code=0; /* RFC2045_ERR codes, here or subentities */

		bool fatal() const
		{
			return code & RFC2045_ERRFATAL;
		}

		std::vector<std::string> describe() const;
	};

	parsing_error errors;

	bool	mime1=false;    /* Mime-Version: 1.0 in effect */

	cte content_transfer_encoding{cte::sevenbit};

	// Used by autoconvert_check(), haslongline is also set in
	// consume_line(), cte::error means no conversion needed

	cte rewrite_transfer_encoding{cte::error}; // Convert to this encoding

	// has8bitchars: content has 8-bit content, quoted-printable encodes
	// characters with high bit set.
	bool has8bitchars{false};

	// hasraw8bitchars: content has 8-bit content
	bool hasraw8bitchars{false};

	// Either long physical line, or a long quoted-printable line
	bool haslongline{false};

	// We look into Content-Type: so often we might as well store its
	// parsed contents
	rfc2231::header content_type{"text/plain", true};

	std::string_view content_type_charset() const;
	std::string_view content_type_boundary() const;
	std::string content_type_start() const;
	const entity *content_type_multipart_signed() const;

	// We don't need to look at these headers here,
	// so we'll just grab them and parse them
	// later.
	std::string content_disposition, content_description,
		content_base,
		content_location, content_md5, content_language;

	// Parsed identifier
	std::string content_id;

	// Strip the fluff

	std::string content_id_value() const;

	// Whether an explicit Content-Type: header was parsed.
	bool has_content_type_header{false};

	// Whether Content-Transfer-Encoding: was parsed
	bool has_content_transfer_encoding{false};

	bool has8bitheader{false};
	bool has8bitbody{false};
	bool has8bitcontentchar{false};

	bool multipart() const
	{
		size_t i=content_type.value.find('/');

		if (i > content_type.value.size())
			i=content_type.value.size();

		return std::string_view{content_type.value.data(), i}
			== "multipart";
	}

	errors_t all_errors() const;
};

/*
  Metadata that's tracked during parsing.

  Keeps track of MIME entities that are currently being parsed.

  Each new entity, when parsing starts, gets push_backed into parsing_entities
  and popped back when its parsing finishes.

*/

struct rfc2045::entity_parse_meta {
	// Currently parsing these entities.
	std::vector<entity *> parsing_entities;

	// How many entities in total were parsed already.
	unsigned short nparsed=0;

	// Whether to parse MIME headers using RFC 2231
	bool do_parse_rfc2231=true;

	// No EOF condition
	struct eof_no {};

	// Reached end of input
	struct eof_yes {};

	// Logical or physical EOF indication
	typedef std::variant<eof_no,
			     eof_yes,
			     entity * // Read multipart boundary delimiter here
			     > eof_t;

	// Stack guard object that takes care of adding and removing
	// pointers from the parsing_entities list.
	struct scope {
		entity_parse_meta &info;

		scope(entity_parse_meta &info, entity *e);

		~scope();
	};

	// Processed a line in the header portion of the currently open
	// MIME entity. Update metrics.

	void consumed_header_line(size_t c);

	// Processed a line in the body portion of the currently open
	// MIME entity. Update metrics.

	void consumed_body_line(size_t c);

	// Set the error bit in the entity being parsed and its parent entities
	void report_error(rfc2045::entity_info::errors_t code);

	// Set the error bit only in the entity being parsed.
	void report_error_here(rfc2045::entity_info::errors_t code);
	bool fatal_error();

	// Default value for the maximum size of an unfolded header, otherwise
	// RFC2045_ERRLONGUNFOLDEDHEADER errors flag gets set.

	static constexpr size_t longunfoldedheadersize=(1024 * 10);
};

namespace rfc2045 {

	template<typename out_iter,
		 typename src_type> class mime_decoder;

	template<typename out_iter> class mime_unicode_decoder_helper;
	template<typename out_iter, typename src_type>
	class mime_unicode_decoder;
}

class rfc2045::entity : public entity_info {

 public:

	template<typename out_iter> class converter;
	template<typename src_type, typename out_chunk> bool decode_body(
		src_type &&src,
		out_chunk &&out
	) const;
	template<typename src_type, typename out_chunk,
		 typename pos_type> bool decode_body_to(
			 src_type &&src,
			 out_chunk &&out,
			 pos_type pos
		 ) const;
	template<typename iter>
	static void tolowercase(iter b, iter e)
	{
		std::for_each(b, e,
			      []
			      (char &c)
			      {
				      if (c >= 'A' && c <= 'Z')
				      {
					      c += 'a'-'A';
				      }
			      });
	}

	template<typename container>
	static inline void tolowercase(container &c)
	{
		tolowercase(c.begin(), c.end());
	}

	static constexpr std::string_view default_mime_header{
		"Mime-Version: 1.0"
	};

	static constexpr std::string_view boundary_msg[]=RFC2045MIMEARRAY;

	// Last parameter to autoconvert()

	struct autoconvert_meta {
		autoconvert_meta();
		~autoconvert_meta();

		std::string appid;

		// The default implementation returns the full_header value
		//
		// The returned string_view's contents must exist at least
		// until either the next call to rwheader(), or until
		// autoconvert() returns.

		virtual std::string_view rwheader(
			// Mime entity whose header is getting passed in
			const entity &e,

			// Header's name in lowercase
			std::string_view lcname,

			// "Header: value<NEWLINE>"
			std::string_view full_header);
	};

	// Utility class for detecting the presence of a proposed boundary
	//
	// The constructor takes a reference to a proposed boundary string.
	// The overloaded () operator receives the raw contents, a chunk at
	// a time.
	//
	// operator bool() indicates whether the MIME boundary delimiter
	// string was found, there was a line in the raw contents that starts
	// with -- followed by the boundary string.

	struct boundary_detector {
		std::string &boundary;
	private:
		bool found=false;
		bool checked=false;
		std::string line;

	public:
		// NOTE: boundary gets tolowercase()d.
		boundary_detector(std::string &boundary);
		~boundary_detector();
		void operator()(const char *, size_t);
		operator bool() const { return found; }
	};

	// Factory for iterators and parsers that use LF(false) or CRLF(true)
	// newline sequence.

	template<bool crlf> struct line_iter {
		template<typename beg_iter_type, typename end_iter_type>
		struct iter;

		template<typename src_type>
		struct headers;

		static constexpr std::string_view eol{
			crlf ? "\r\n":"\n"
		};

		template<typename src_type> static bool try_boundary(
			src_type &&src,
			std::string boundary,
			const entity &e
		);

		template<typename out_iter,
			 typename src_type>
		static auto autoconvert(const entity &e, out_iter &&closure,
					src_type &src,
					autoconvert_meta &metadata)
		{
			unsigned counter=0;

			autoconvert(e, closure, src, counter, metadata);

			if constexpr (!std::is_same_v<out_iter, out_iter &>)
			{
				return closure;
			}
		}

		template<typename out_iter,
			 typename src_type>
		static void autoconvert(const entity &e, out_iter &closure,
					src_type &src, unsigned &counter,
					autoconvert_meta &metadata);

		template<typename out_iter,
			 typename src_type>
		static void autoconvert_entity(
			const entity &e,
			out_iter &closure,
			src_type &src,
			autoconvert_meta &metadata);
	};

	// Allow default constructor to be invoked if this entity is a part
	// of a tuple
	entity(std::tuple<>) noexcept : entity() {}

	entity() noexcept;

	entity(const entity &) noexcept;

	entity(entity &&) noexcept;

	entity &operator=(const entity &) noexcept;

	entity &operator=(entity &&) noexcept;

private:
	void update_parent_ptr();
public:

	template<typename visitor_t>
	bool visit_all(visitor_t &&visitor) const
	{
		if (!visitor(*this))
			return false;

		for (auto &se:subentities)
		{
			if (!se.visit_all(std::forward<visitor_t>(visitor)))
				return false;
		}
		return true;
	}

	template<typename line_iter_type>
	void parse(line_iter_type &iter);

	/*
	  Prepare to read the headers of the next mime section.

	  This is called immediately upon encountering an
	  rfc2045_message_content_type, or at the beginning of each multipart
	  sub-entity.
	*/

	template<typename line_iter_type>
	entity &start(line_iter_type &iter)
	{
		subentities.emplace_back();

		auto &subentity=subentities.back();

		subentity.parent_entity=this;

		// Initialize subentity positions appropriately. In case of a
		// rfc2045_message_content_type we just started its body,
		// and my startbody and endbody are the same. In case of a
		// multipart subentity, my endbody is where it starts, so
		// using endbody in either case gets this right.

		subentity.startpos=
			subentity.startbody=subentity.endbody=endbody;
		iter.entered_header();

		return subentity;
	}

	static std::string new_boundary(unsigned &counter);

	template<typename src_type> std::string new_boundary(
		src_type &&src,
		unsigned &counter
	) const;

	// Determine whether MIME entities should have their Content-Type
	// and/or Content-Transfer-Encoding header updated accordingly.
	//
	// Drop the sub-entities of multipart/signed MIME entities, so that
	// it's treated as a single MIME entity (and never rewritten).

	bool autoconvert_check(convert rwmode);

	template<typename closure_type>
	void enumerate(closure_type &&closure) const
	{
		std::string id;

		id.reserve(7);

		id="1";

		enumerate(id, std::forward<closure_type>(closure));
	}

private:
	template<typename closure_type>
	void enumerate(std::string &id, closure_type &&closure) const
	{
		char buffer[15];

		closure(std::string_view{id}, *this);

		size_t s=id.size()+1;

		id += ".1";

		size_t n=1;
		for (auto &sube:subentities)
		{
			sube.enumerate(id,
				       std::forward<closure_type>(closure));
			id.resize(s);
			++n;
			auto ret=std::to_chars(std::begin(buffer),
					       std::end(buffer), n);

			id.insert(id.end(), buffer, ret.ptr);
		}
		id.resize(s-1);
	}

	template<typename T>
	static T *find(T *root, std::string_view id);

public:
	entity *find(std::string_view id)
	{
		return find(this, id);
	}
	const entity *find(std::string_view id) const
	{
		return find(this, id);
	}

	entity *find_content_type(std::string_view content_type);
	const entity *find_content_type(std::string_view content_type) const;

	struct dsn {
		std::string_view action;
		std::string_view original_recipient;
		std::string_view final_recipient;
	};

	using dsn_callback_t=std::function<void (const dsn &)>;
private:
	class dsn_handler {

		std::string linebuf;
		std::string origreceip;
		std::string finalreceip;
		std::string action;
		const dsn_callback_t &callback;
	public:
		dsn_handler(const dsn_callback_t &);

		const rfc2045::entity *report(const rfc2045::entity &entity);
		void finish();
		void operator()(const char *, size_t);
	};
public:
	template<typename src_type>
	bool getdsn(src_type &src, const dsn_callback_t &callback) const
	{
		dsn_handler handler{callback};

		auto report=handler.report(*this);

		if (!report)
			return false;

		rfc2045::mime_decoder decoder{
			handler,
			src
		};

		decoder.decode_header=false;
		decoder.decode_body=true;
		decoder.decode_subentities=false;

		decoder.decode(*report);

		handler.finish();
		return true;
	}

	// Return ptr to signed content if ptr is a multipart/signed.

	const entity *is_multipart_signed() const;

	// Return ptr to encrypted content if ptr is a multipart/encrypted.
	const entity *is_multipart_encrypted() const;

	// Return whether MIME content has any signed or encrypted content.

	bool has_mimegpg() const;

	// Returns a value if this is a multipart/mixed section generated
	// by mimegpg, and returns the GnuPG return code.
	//

	std::optional<int> is_decoded() const;

	/*
	** If is_multipart_decoded(), then return the ptr to the decoded
	** content (note - if decryption failed, NULL is returned).
	*/

	const entity *decoded_content() const;

	// If is_multipart_signed(), return ptr to the signed content.

	const entity *signed_content() const;
};

template<bool crlf>
template <typename beg_iter_type, typename end_iter_type>
struct rfc2045::entity::line_iter<crlf>::iter : entity_parse_meta {

	beg_iter_type &b;
	end_iter_type &e;

	// Buffered contents of the current line being processed
	std::string buffer;

	// Whether the current line ended with NL

	// This is used to detect messages that don't end in NL
	bool buffer_has_trailing_nl{true};

	// If a logical line in a quoted-printable MIME entity exceeds these
	// number of characters, this set the RFC2045_ERRLONGQUOTEDPRINTABLE
	// flag.

	size_t longquotedlinesize=500;

	// Whether the previously consumed line was nonempty
	bool prev_line_was_empty{true};

	// Default value for the maximum size of an unfolded header, otherwise
	// RFC2045_ERRLONGUNFOLDEDHEADER errors flag gets set.
	size_t longunfoldedheadersize=entity_parse_meta::longunfoldedheadersize;

	// The maximum number of MIME entities that are parsed, and the
	// maximum nesting level.
	size_t mimeentityparselimit=300;
	size_t mimeentitynestedlimit=20;

	struct body_line{};
	struct header_line{};
	struct empty_header_line{};

	// What kind of a line was most recently read in.
	//
	// In the beginning, there was a header line...
	//
	// Then there was an empty header line...
	//
	// Finally, a body line followed...
	std::variant<body_line,
		     header_line,
		     empty_header_line> current_line_type{
		header_line{}
	};

	// ... until another genesis, another header for another MIME section.
	//
	// This is called from start(), upon creating a new MIME subentity,
	// so we're now reading headers.
	void entered_header()
	{
		current_line_type=header_line{};
	}

	// For convenience, entered_body() is also used in the testsuite
	void entered_body()
	{
		current_line_type=body_line{};
	}

	// Whether the current line is considered to be in the header section.
	bool in_header()
	{
		return !std::holds_alternative<body_line>(current_line_type);
	}

	iter(beg_iter_type &b,
	     end_iter_type &e)
		: b{b}, e{e}
	{
	}

	constexpr size_t eol_size() { return crlf ? 2:1; }

	// Have we reached logical or actual end of input?
	//
	// Inspects the current line to see if it matches
	// any currently open mime entity's boundary, otherwise
	// whether the underlying input's iterator reached
	// the ending iterator value.
	//
	// eof() caches the result of a search for an open multipart
	// entity with the matching boundary delimiter, we do it once.
	// The cache is cleared in consume_line().

	std::optional<entity *> cached_boundary_found;

	// Buffer for boundary searches
	std::string boundary_chk_buf;

	eof_t eof() {
		const auto &[p, q]=current_line();

		if (buffer.empty() && b == e)
			return eof_yes{};

		if (!cached_boundary_found)
		{
			entity *found_entity_boundary=nullptr;

			if (q-p > 2 &&
			    buffer[0] == '-' &&
			    buffer[1] == '-')
			{
				boundary_chk_buf.assign(p+2, q);

				tolowercase(boundary_chk_buf);

				for (auto e:parsing_entities)
				{
					auto b=e->content_type_boundary();
					if (b.empty())
						continue;
					if (b.size() >
					    boundary_chk_buf.size())
					{
						continue;
					}

					if (b == std::string_view(
						    boundary_chk_buf.data(),
						    b.size()))
					{
						found_entity_boundary=e;
						break;
					}
				}
			}
			cached_boundary_found=found_entity_boundary;
		}

		if (*cached_boundary_found)
			return *cached_boundary_found;
		return eof_no{};
	}

	// Ensure that buffer has a complete line, including
	// EOL.
	//
	// Returns a tuple with an iterator to the beginning and an
	// iterator to the end of line character (the EOL character itself,
	// and not one past it).
	//
	// For \r\n EOL sequence this will be a pointer to \r
	//
	// Quietly prunes lines to 1024 characters. If
	// the line, including EOL, is less than 1024 chars
	// the returned ending iterator is not buffer.end(). a
	// buffer.end() return indicates a truncated line.
	//
	// The ending iterator is saved in cached_eol_iter,
	// and if this is called again it'll just return it,
	// saving all the work. consume_line() clears the
	// cached_eol_iter.

	std::optional<std::string::iterator> cached_eol_iter;

	std::tuple<std::string::iterator,
		   std::string::iterator> current_line() {

		if (cached_eol_iter)
			return { buffer.begin(), *cached_eol_iter };

		auto p=do_current_line();
		cached_eol_iter=p;

		auto b=buffer.begin();

		if (std::holds_alternative<header_line>(current_line_type) &&
		    b == p)
		{
			current_line_type=empty_header_line{};
		} else if (std::holds_alternative<empty_header_line>(
				   current_line_type
			   ))
		{
			entered_body();
		}

		return {b, p};
	}

	std::string::iterator do_current_line() {

		buffer.clear();

		char prev_ch=0;

		bool read_something=false;
		while (b != e)
		{
			read_something=true;
			if constexpr (!crlf)
			{
				if (buffer.size() == 1024)
					break;

				char c=*b++;

				buffer.push_back(c);
				if (c == '\n')
				{
					return --buffer.end();
				}
			}
			else
			{
				char c=*b;

				if (buffer.size() ==
				    (c == '\r' ? 1023:1024))
				{
					break;
				}

				++b;
				buffer.push_back(c);
				if (c == '\n' &&
				    prev_ch == '\r')
				{
					return buffer.end()-2;
				}
				prev_ch=c;
			}
		}

		// If we got here and we read_something this means that the
		// last line does not end in a newline.
		//
		// If we got here and we didn't read_something, this means that
		// we were already at EOF when we_do_current_line.

		if (read_something)
			buffer_has_trailing_nl=false;
		return buffer.end();
	}

	// The current line is no longer looked at. consume_line(), then
	// update the header/body_position.

	size_t consume_line_and_update_position(
		rfc2045::entity &entity,
		cte encoding,
		bool &has8bit)
	{
		// Note where we are before calling consume_line.

		bool was_in_header=in_header();

		size_t c{consume_line(entity, encoding, has8bit)};

		prev_line_was_empty=c <= eol_size();
		if (was_in_header)
			consumed_header_line(c);
		else
			consumed_body_line(c);

		return c;
	}

	// Remove the current_line() from the input.
	//
	// If the input line was truncated in current_line(),
	// quietly discards what was unread.
	//
	// Returns the number of characters in the current_line(),
	// including the EOL terminator (LF or CRLF), the
	// character count is accurate even if the next line
	// in buffer was truncated.

private:
	bool isnybble(char c)
	{
		return (c >= '0' && c <= '7') ||
			(c >= 'a' && c <= 'f') ||
			(c >= 'A' && c <= 'F');
	}

	size_t unquoted_line_size{0};

	void check_qp(rfc2045::entity &entity,
		      char prev_prev_ch, char prev_ch, char ch, bool &has8bit)
	{
		++unquoted_line_size;

		if (ch & 0x80)
			report_error(RFC2045_ERR8BITINQP);

		if (prev_prev_ch == '=')
		{
			unquoted_line_size -= 2;

			if (!isnybble(prev_ch) || !isnybble(ch))
			{
				report_error(RFC2045_ERRBADHEXINQP);
			}
		}

		if (prev_ch == '=' && ch > '7')
		{
			has8bit=true;

			entity.has8bitchars=true;
			entity.hasraw8bitchars=true;
		}
	}

	void check_base64(char c)
	{
		if (c >= '0' && c <= '9')
			return;

		if (c >= 'A' && c <= 'Z')
			return;

		if (c >= 'a' && c <= 'z')
			return;

		switch (c) {
		case '+':
		case '/':
		case '=':
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			return;
		}

		report_error(RFC2045_ERRINVALIDBASE64);
	}

	void check_qp_toolong(rfc2045::entity &entity, char last_ch)
	{
		if (last_ch == '=')
		{
			// Don't count trailing =

			--unquoted_line_size;
			return;
		}

		if (unquoted_line_size > longquotedlinesize)
		{
			entity.haslongline=true;
			report_error_here(
				RFC2045_ERRLONGQUOTEDPRINTABLE
			);
		}

		unquoted_line_size=0;
	}

	size_t consume_line(rfc2045::entity &entity,
			    cte encoding, bool &has8bit)
	{
		bool was_in_header=in_header();
		const auto &[p,q]=current_line();

		cached_eol_iter.reset();
		cached_boundary_found.reset();

		char prev_prev_ch=0;
		char prev_ch=0;

		if (encoding == cte::qp)
		{
			for (auto i=p; i != q; ++i)
			{
				check_qp(entity,
					 prev_prev_ch, prev_ch, *i, has8bit);

				prev_prev_ch=prev_ch;
				prev_ch=*i;
			}
		}
		else
		{
			for (auto i=p; i != q; ++i)
			{
				if (static_cast<unsigned char>(*i) & 0x80)
				{
					if (was_in_header)
					{
						report_error_here(
							RFC2045_ERR8BITHEADER
						);
					}

					has8bit=true;
					entity.hasraw8bitchars=true;
				}

				if (encoding == cte::base64)
				{
					check_base64(*i);
				}
			}
		}

		if (q != buffer.end() ||
		    b == e)
		{
			// Not truncated.

			if (encoding == cte::qp)
			{
				check_qp_toolong(entity, prev_ch);
			}

			size_t s=buffer.size();

			if (s > longquotedlinesize)
				entity.haslongline=true;

			buffer.clear();
			return s;
		}

		entity.haslongline=true;

		// Truncated. Do more work, read until the
		// true EOL.

		size_t c=buffer.size();

		buffer.clear();

		while (1)
		{
			if (b == e)
			{
				if (encoding == cte::qp)
				{
					check_qp_toolong(entity, prev_ch);
				}
				break;
			}

			auto ch=static_cast<unsigned char>(*b++);
			++c;

			if (encoding == cte::qp)
			{
				check_qp(entity,
					 prev_prev_ch, prev_ch, ch, has8bit);
			}
			else
			{
				if (encoding == cte::base64)
				{
					check_base64(ch);
				}
				if (ch & 0x80)
				{
					if (was_in_header)
					{
						report_error_here(
							RFC2045_ERR8BITHEADER
						);
					}

					has8bit=true;
					entity.hasraw8bitchars=true;
				}
			}
			if ((!crlf || prev_ch == '\r') &&
			    ch == '\n')
			{
				if (encoding == cte::qp)
				{
					// Subtract the EOL

					unquoted_line_size -=
						(crlf ? 2:1);

					check_qp_toolong(entity,
						crlf ? prev_prev_ch:prev_ch
					);
				}
				break;
			}
			prev_prev_ch=prev_ch;
			prev_ch=ch;
		}
		return c;

	}
public:

	// Return the current_line(), as long as we're still in the header
	// section, otherwise returns the same beginning/ending iterator,
	// simulating an empty line at the end of the header.

	std::tuple<std::string::iterator,
		   std::string::iterator> current_header_line()
	{
		if (std::holds_alternative<body_line>(current_line_type))
		{
			auto b=buffer.begin();

			return {b, b};
		}

		return current_line();
	}

	// Collect the next folded line into a std::string
	// line.
	//
	// Silently truncate the next folded line to 10kb
	//
	// Parse out the header name from the beginning of the
	// line, and convert to lowercase. Returns string_views
	// for the header name and contents.
	std::tuple<std::string_view,
		   std::string_view>
		next_folded_header_line(entity &e,
					std::string &line)
	{
		line.clear();

		if (!std::holds_alternative<eof_no>(eof()))
		{
			// We are getting a real EOF indication, or
			// a MIME boundary delimiter. Officially enter
			// the body part of the MIME entity, and call it quits.
			entered_body();
			return std::tuple("","");
		}

		auto beg_end_line=current_header_line();

		auto &[bp, ep]=beg_end_line;

		line.insert(line.end(), bp, ep);

		while (1)
		{
			// Here, we're looking either at the first physical,
			// line, read above, or at a continuation line, read
			// below, and looped back here.
			//
			// If we just read an empty line, as the first physical
			// line, we're done. However, if we already read the
			// the first physical line, and it was not empty, we
			// want to stop before accounting for the empty line,
			// so that the next time here we will return with an
			// empty line.

			if (bp == ep && !line.empty())
				break;

			consume_line_and_update_position(e, cte::sevenbit,
							 e.has8bitheader);

			if (!std::holds_alternative<eof_no>(
				    eof()
			    ))
			{
				// Before returning, we enter the body
				// portion, as a formality.
				entered_body();
				break; // No need to check the line, it's empty.
			}

			// Peak at the next line, is it still a header, and
			// does it begin with whitespace?
			beg_end_line=current_header_line();

			auto s=bp;

			while (bp != ep && (
				       *bp == ' ' || *bp == '\t'))
			{
				++bp;
			}

			if (s == bp)
				break;

			// Back up, the last whitespace will
			// be a space. Do the accounting
			// based on the included space.

			--bp;

			size_t l=ep-bp;

			if (l+buffer.size() > longunfoldedheadersize)
			{
				l=longunfoldedheadersize-buffer.size();
				report_error_here(
					RFC2045_ERRLONGUNFOLDEDHEADER
				);
			}
			// The first character is always a space

			if (l)
			{
				line.push_back(' ');
				++bp;
				--l;
			}
			line.insert(line.end(), bp, bp+l);
		}

		// Proactively disarm any practical jokes
		line.erase(
			std::remove_if(line.begin(), line.end(),
				       [](char &c)
				       {
					       return c == 0 || c == '\r';
				       }), line.end());


		auto lb=line.begin(), le=line.end(),
			p=std::find(lb, le, ':');

		tolowercase(lb, p);
		auto q=std::find_if(
			p, le,
			[]
			(char &c)
			{
				return c != ':' &&
					c != ' ' &&
					c != '\t';
			});

		while (le > q)
		{
			switch(le[-1]) {
			case ' ':
			case '\t':
				--le;
				continue;
			}
			break;
		}
		return std::tuple(
			std::string_view(line.c_str(), p-lb),
			std::string_view(line.c_str()+(
						 q-lb
					 ), le-q));
	}
};


template<typename line_iter_type>
void rfc2045::entity::parse(line_iter_type &iter)
{
	entity_parse_meta::scope scope{iter, this};

	std::string line;

	bool duplicate_content=false;
	iter.buffer_has_trailing_nl=true; // Reset for a clean slate
	while (iter.in_header())
	{
		auto [name, header]=iter.next_folded_header_line(*this, line);

		startbody=endbody;

		if (name == "mime-version")
		{
			auto b=header.begin(), e=header.end();

			if (b < e && *b == '1' && ++b < e && *b == '.')
			{
				mime1=true;
			}
		}

		if (name == "content-type")
		{
			content_type=rfc2231::header{header,
						    iter.do_parse_rfc2231};

			if (has_content_type_header)
				duplicate_content=true;

			has_content_type_header=true;

			content_type.lowercase_value("charset");
			content_type.lowercase_value("boundary");
			content_type.lowercase_value("format");
			content_type.lowercase_value("delsp");
		}
		if (name == "content-transfer-encoding")
		{
			if (has_content_transfer_encoding)
				duplicate_content=true;

			content_transfer_encoding=to_cte(
				header.size() ? *header.data():0,
				header.size() > 1 ? header.data()[1]:0);

			has_content_transfer_encoding=true;
			if (content_transfer_encoding==cte::error)
			{
				iter.report_error(RFC2045_ERRUNKNOWNTE
						  | RFC2045_ERRFATAL);
				return;
			}
		}
		if (name == "content-disposition")
		{
			content_disposition=header;
		}
		if (name == "content-md5")
		{
			content_md5=header;
		}
		if (name == "content-location")
		{
			content_location.clear();

			rfc822::tokens tokens{header};

			content_location.reserve(
				rfc822::tokens::unquote(
					tokens.begin(),
					tokens.end(),
					rfc822::length_counter{}));

			rfc822::tokens::unquote(
				tokens.begin(),
				tokens.end(),
				std::back_inserter(content_location));
		}
		if (name == "content-base")
		{
			content_base.clear();

			rfc822::tokens tokens{header};

			content_base.reserve(
				rfc822::tokens::unquote(
					tokens.begin(),
					tokens.end(),
					rfc822::length_counter{}));

			rfc822::tokens::unquote(
				tokens.begin(),
				tokens.end(),
				std::back_inserter(content_base));
		}
		if (name == "content-language")
		{
			content_language=header;
		}
		if (name == "content-description")
		{
			content_description=header;
		}
		if (name == "content-id")
		{
			content_id.clear();
			content_id.reserve(header.size());

			rfc822::tokens tokens{header};
			rfc822::addresses addresses{tokens};

			if (addresses.size())
				addresses.front().unquote_name(
					std::back_inserter(content_id)
				);
		}
	}

	startbody=endbody;

	if (hasraw8bitchars)
	{
		iter.report_error_here(RFC2045_ERR8BITHEADER);
		hasraw8bitchars=false; // So it reflects body content.
	}
	bool is_multipart=false;

	// Bail out of the MIME complexity is unreasonable, indicates a
	// possible attack.

	if (++iter.nparsed > iter.mimeentityparselimit ||
	    iter.parsing_entities.size() > iter.mimeentitynestedlimit)
	{
		iter.report_error(RFC2045_ERR2COMPLEX |
				  RFC2045_ERRFATAL);
		return;
	}
	else if (!mime1)
	{
		content_type=rfc2231::header{"text/plain", true};
		content_transfer_encoding=cte::sevenbit;
	}
	else
	{
		if (duplicate_content)
		{
			iter.report_error(RFC2045_ERRDUPLICATECONTENT |
					  RFC2045_ERRFATAL);
		}

		bool is_message=
			rfc2045::message_content_type(content_type.value);

		if (is_message &&
		    (content_transfer_encoding == cte::sevenbit ||
		     content_transfer_encoding == cte::eightbit))
		{
			start(iter).parse(iter);
			return;
		}

		if (is_message)
			content_type.value="message/octet-stream";
		if (multipart())
		{
			auto b=content_type_boundary();
			size_t l=b.size();
			auto lp=b.data();

			if (l == 0)
			{
				iter.report_error(
					RFC2045_ERRBADBOUNDARY |
					RFC2045_ERRFATAL);
				return;
			}

			// Sanity check: unique boundaries.
			is_multipart=true;

			for (auto &e:iter.parsing_entities)
			{
				if (e == this)
					continue; // Last one is us

				auto eb=e->content_type_boundary();
				size_t m=eb.size();

				if (m == 0) // message/subtype
					continue;

				if (m > l)
					m=l;
				if (std::equal(lp, lp+m, eb.data()))
				{
					iter.report_error(
						RFC2045_ERRBADBOUNDARY |
						RFC2045_ERRFATAL
					);
					return;
				}
			}
		}
	}

	bool multipart_ongoing=false;

	if (!is_multipart)
	{
		auto ctb=content_type.parameters.find("boundary");
		if (ctb != content_type.parameters.end())
			// Get rid of any strays...
			content_type.parameters.erase(ctb);
	}
	else
	{
		if (content_transfer_encoding == cte::base64)
		{
			iter.report_error(
				RFC2045_ERRINVALIDBASE64 |
				RFC2045_ERRFATAL
			);
		}
		multipart_ongoing=true;
		content_transfer_encoding=cte::eightbit; // Meaningless
	}

	// So, if this is a multipart entity, we start scanning for boundary
	// delimiters.

	while (multipart_ongoing)
	{
		auto status=iter.eof();

		entity **boundary_ptr;

		while ((boundary_ptr=std::get_if<entity *>(&status)) != nullptr
		       && !iter.fatal_error())
		{
			// If we see a parent entity's boundary, we've got
			// problems.

			if (*boundary_ptr != this)
			{
				iter.report_error(
					RFC2045_ERRWRONGBOUNDARY |
					RFC2045_ERRFATAL);
				return;
			}

			// Adjust endbody of last subentity to be /before/
			// the trailing crlf, according to MIME spec,
			// and recursively do the adjustment to its last
			// subentity, if there is one.
			//
			// This also means that it has one fewer nlines and
			// nbodylines.

			auto adjusted_endbody=endbody - iter.eol_size();

			auto *se= &subentities;

			while (!se->empty())
			{
				auto &last_se=se->end()[-1];

				if (adjusted_endbody < last_se.endbody)
				{
					last_se.endbody=adjusted_endbody;

					// If the most recent line was empty,
					// its newline is logically a part of
					// the multipart boundary delimiter,
					// and we subtract one. However if
					// it was a non-empty line it becomes
					// a line without a trailing newline,
					// but it still counts as a line.

					if (iter.prev_line_was_empty)
					{
						if (last_se.nbodylines)
							--last_se.nbodylines;
						if (last_se.nlines)
							--last_se.nlines;
					}
					else
					{
						last_se.no_terminating_nl=true;
					}
				}
				se= &last_se.subentities;
			}

			// Process the boundary line.

			auto line=iter.current_line();
			auto &[b, e]=line;

			// We know that the line starts with --, at least.

			if (e-b >= 4 && e[-2] == '-' && e[-1] == '-')
			{
				// Terminating boundary delimiter. We'll
				// consume it, below, then swallow things up
				// until the end of this entity.

				multipart_ongoing=false;
				break;
			}

			iter.consume_line_and_update_position(
				*this,
				cte::sevenbit,
				has8bitbody);

			auto &new_subentity=start(iter);

			new_subentity.mime1=true;

			if (content_type.value == "multipart/digest")
				new_subentity.content_type.value=
					"message/rfc822";

			new_subentity.parse(iter);

			if (iter.fatal_error())
				return;
			status=iter.eof();
		}
		if (std::holds_alternative<entity_parse_meta::eof_yes>(status))
			return;

		if (iter.fatal_error())
			break;

		iter.consume_line_and_update_position(*this, cte::sevenbit,
						      has8bitbody);
	}

	while (std::holds_alternative<entity_parse_meta::eof_no>(iter.eof()))
	{
		iter.consume_line_and_update_position(
			*this,
			content_transfer_encoding,
			*(content_transfer_encoding == cte::qp ?
			  &has8bitcontentchar:&has8bitbody)
		);
	}

	if (!parent_entity && !iter.buffer_has_trailing_nl)
		no_terminating_nl=true;
}

/* Push interface for the rfc2045 parser

   Push content to parse via rfc2045 one chunk at a time.

   A separate execution thread gets started, which invokes parse(), and
   the parsed chunks are fed to it.

   entity_parser<bool> parser;

   parser.parse(std::istreambuf_iterator<char>(stream),
                std::istreambuf_iterator<char>{});

   entity e=parser.parsed_entity();

   The template parameter specifies whether the MIME entity uses LF (false)
   or CRLF (true) newline sequences.

   After constructing parse() can be called repeatedly to define the MIME
   entity to parse. Each call to parse() might block to wait for the
   execution thread to finish parsing the previous block.

   After the entire MIME entity is parse()d, parsed_entity() stops the
   execution thread and returns the parsed entity.
 */

class rfc2045::entity_parser_base {

protected:
	entity entity_getting_parsed;
	std::thread parsing_thread;

	std::mutex m;
private:
	std::condition_variable c;

	bool thread_finished{false};
	bool end_of_parse{false};

	bool has_content_to_parse{false};
	std::string content_to_parse;

public:
	entity_parser_base();
	~entity_parser_base();


#ifndef RFC2045_ENTITY_PARSER_TEST
#define RFC2045_ENTITY_PARSER_TEST(a) do {} while (0)
#define RFC2045_ENTITY_PARSER_DECL(a) do {} while (0)
#endif

	template<typename beg_iter, typename end_iter>
	void parse(beg_iter &&b, end_iter &&e)
	{
		std::unique_lock lock{m};

		c.wait(lock,
		       [this]
		       {
			       return thread_finished || end_of_parse ||
				       !has_content_to_parse;
		       });

		if (thread_finished || end_of_parse)
		{
			RFC2045_ENTITY_PARSER_TEST("parser: chunk (ignored)");
			return;
		}

		content_to_parse.clear();

		std::copy(b, e, std::back_inserter(content_to_parse));
		if (content_to_parse.empty())
			return;

		has_content_to_parse=true;
		RFC2045_ENTITY_PARSER_TEST("parser: chunk");
		c.notify_all();
	}

	bool get_next_chunk(std::unique_lock<std::mutex> &, std::string &chunk);

	entity &&parsed_entity();
};

template<bool crlf>
class rfc2045::entity_parser : entity_parser_base {

public:
	entity_parser();
	~entity_parser();

	using entity_parser_base::parse;
	using entity_parser_base::parsed_entity;
};

// Parts of rfc2045::entity::line_iter::headers that does not depend on
// template parameters.

struct rfc2045::headers_base {

protected:
	const size_t empty_line_size;

	bool line_read{false};
	size_t left; // How many characters left until the end of headers.

	std::string header_line;

public:
	virtual bool next()=0;

	bool name_lc{true};
	bool keep_eol{false};
	size_t max{16384};

	headers_base(size_t empty_line_size);
	std::string_view current_header();
	std::tuple<std::string_view, std::string_view> name_content();
	std::tuple<std::string, bool> convert_name_check_empty();

	bool finished() const
	{
		return left == 0;
	}
};

template<bool crlf>
template <typename src_type>
struct rfc2045::entity::line_iter<crlf>::headers : headers_base {

	src_type &src;

	bool stop_on_empty_line=true;
public:

	// Read the header portion of this MIME entity
	headers(const entity &e, src_type &src)
		: headers_base{crlf ? 2:1}, src{src}
	{
		const auto err_value=static_cast<
			typename src_type::pos_type>(
				static_cast<
				typename src_type::off_type>(-1));

		left=e.startbody-e.startpos;
		if (src.pubseekpos(e.startpos) == err_value)
			left=0;
	}

	// Read the headers at this position.

	headers(src_type &src, size_t counter=0)
		: headers_base{crlf ? 2:1}, src{src}
	{
		const auto err_value=static_cast<
			typename src_type::pos_type>(
				static_cast<
				typename src_type::off_type>(-1));

		auto curpos=src.pubseekoff(0, std::ios_base::cur,
					   std::ios_base::in);
		auto endpos=src.pubseekoff(0, std::ios_base::end,
					   std::ios_base::in);

		// Pretend the whole thing is a header. We'll stop at an
		// empty line.

		if (curpos == err_value || endpos == err_value ||
		    src.pubseekpos(curpos, std::ios_base::in) == err_value)
		{
			left=0;
		}
		else
		{
			left=endpos-curpos;
		}

		if (counter > 0 && counter <= left)
		{
			left=counter;
			stop_on_empty_line=false;
		}

	}

	~headers()=default;

	headers(const headers &)=default;
	headers(headers &&)=default;

	headers &operator=(const headers &)=delete;
	headers &operator=(headers &&)=delete;

	bool next() override
	{
		header_line.clear();
		line_read=true;

		if (!left)
			return false;

		char prev_ch=0;
		size_t line_size=0;
		bool skip_leading_spaces=false;

		while (1)
		{
			if (left == 0)
			{
				line_size=header_line.size();
				break;
			}
			auto ch=src.sbumpc();
			--left;
			if (ch == std::streambuf::traits_type::eof())
			{
				left=0;
				break;
			}

			if constexpr (crlf)
			{
				if (ch == '\r')
					line_size=header_line.size();
			}

			if ((ch == ' ' || ch == '\t') && skip_leading_spaces)
				;
			else
			{
				if (header_line.size() < max)
					header_line.push_back(ch);
				skip_leading_spaces=false;
			}

			if constexpr (crlf)
			{
				if (prev_ch != '\r' || ch != '\n')
				{
					prev_ch=ch;
					continue;
				}
			}
			else
			{
				if (ch != '\n')
				{
					line_size=header_line.size();
					continue;
				}
			}

			if (!keep_eol)
				header_line.resize(line_size);
			skip_leading_spaces=false;

			if (left)
			{
				switch (src.sgetc()) {
				case ' ':
				case '\t':
					if (!keep_eol)
					{
						if (header_line.size() < max)
							header_line.push_back(
								' '
							);
						skip_leading_spaces=true;
					}
					continue;
				default:
					break;
				}
			}
			break;
		}

		// Empty line results in an EOF, here, if a MIME entity
		// was not passed in the constructor, and no explicit byte
		// count was given.

		if (line_size <= empty_line_size && stop_on_empty_line)
			left=0;

		if (name_lc)
		{
			auto b=header_line.begin(), e=header_line.end();
			auto p=std::find(b, e, ':');

			entity::tolowercase(b, p);
		}
		return true;
	}
};

extern template class rfc2045::entity_parser<false>;
extern template class rfc2045::entity_parser<true>;

template<typename out_iter>
class rfc2045::entity::converter : unicode::iconvert {
	out_iter &out;

public:
	converter(out_iter &out) : out{out} {}

	using unicode::iconvert::operator();
	using unicode::iconvert::begin;

	bool end(bool &flag)
	{
		return unicode::iconvert::end(flag);
	}
private:
	int converted(const char *p, size_t n) override
	{
		out(p, n);
		return 0;
	}
};

template<typename out_iter,
	 typename src_type>
class rfc2045::mime_decoder {

	src_type &src;

	out_iter out;
	std::string charset;

 public:
	template<typename T>
	mime_decoder(T &&out, src_type &src,
		     std::string_view charset="")
		: src{src}, out{std::forward<T>(out)},
		  charset{charset.begin(), charset.end()}
	{
	}

	bool decode_header=true;
	bool decode_body=true;
	bool add_eol=false;
	bool header_name_lc=true;
	bool header_name_suppress=false;
	bool decode_subentities=true;

	bool decoding_error=false;
	bool report_decoding_error=true;

	std::function<bool(std::string_view, std::string_view)> headerfilter;
	std::function<void(std::string_view)> headerdone;

	template<bool crlf=false>
	void decode(const rfc2045::entity &e);
	~mime_decoder()=default;
};

template<typename out_iter> class rfc2045::mime_unicode_decoder_helper {

	out_iter out;

public:
	template<typename T>
	mime_unicode_decoder_helper(T && out)
		: out{std::forward<T>(out)}
	{
	}

	void operator()(const char *ptr, size_t cnt) const
	{
		cnt /= sizeof(char32_t);
		out(reinterpret_cast<const char32_t *>(ptr), cnt);
	}
};

namespace rfc2045 {
	template<typename out_iter,
		 typename src_type>
	mime_decoder(out_iter &, src_type &, std::string_view="")
		-> mime_decoder<out_iter &, src_type>;

	template<typename out_iter,
		 typename src_type>
	mime_decoder(out_iter &&, src_type &, std::string_view="")
		-> mime_decoder<out_iter, src_type>;
}

template<typename out_iter,
	 typename src_type> class rfc2045::mime_unicode_decoder
	: public mime_unicode_decoder_helper<out_iter>,
	  public mime_decoder<mime_unicode_decoder_helper<out_iter> &,
				      src_type> {

public:
	template<typename T>
	mime_unicode_decoder(T && out, src_type &src)
		: mime_unicode_decoder_helper<out_iter>{
				std::forward<T>(out)
			},
		mime_decoder<mime_unicode_decoder_helper<out_iter> &,
			     src_type>{*this, src, unicode_u_ucs4_native}
	{
	}
};

namespace rfc2045 {
	template<typename out_iter,
		 typename src_type>
	mime_unicode_decoder(out_iter &, src_type &, std::string="")
		-> mime_unicode_decoder<out_iter &, src_type>;

	template<typename out_iter,
		 typename src_type>
	mime_unicode_decoder(out_iter &&, src_type &, std::string="")
		-> mime_unicode_decoder<out_iter, src_type>;

	// MIME decoder might emit the decoded chunk in small pieces. To be
	// efficient we'll combine them into bigger chunk. Take the real
	// output sink, and present an output sink that chunks things in
	// BUFSIZ-sized bytes.

	template<typename out_iter>
	struct chunking_decoder {
		out_iter &real_out;
		char buf[BUFSIZ];
		size_t s{0};
	public:
		chunking_decoder(out_iter &real_out) : real_out{real_out}
		{
		}

		void flush()
		{
			if (s == 0)
				return;
			real_out(buf, s);
			s=0;
		}

		void operator()(const char *ptr, size_t cnt)
		{
			if (cnt > BUFSIZ-s)
			{
				flush();
				if (cnt > BUFSIZ)
				{
					real_out(ptr, cnt);
					return;
				}
			}
			std::copy(ptr, ptr+cnt, buf+s);
			s += cnt;
		}
	};
}

template<typename out_iter,
	 typename src_type>
template<bool crlf>
void rfc2045::mime_decoder<out_iter, src_type>::decode(
	const rfc2045::entity &e
)
{
	auto &eol=rfc2045::entity::line_iter<crlf>::eol;

	if (decode_header)
	{
		typename rfc2045::entity::line_iter<crlf>::template headers<
			src_type> parser{e, src	};

		parser.name_lc=header_name_lc;
		std::string header;

		do
		{
			const auto &[name, content]=parser.name_content();

			if (headerfilter && !name.empty() &&
			    !headerfilter(name, content))
				continue;

			if (!header_name_suppress)
			{
				out(name.data(), name.size());

				if (!name.empty())
				{
					out(": ", 2);
				}
			}

			header.clear();
			rfc822::display_header(name, content, charset,
					       std::back_inserter(header));

			header += eol;
			out(header.data(), header.size());

			if (headerdone && !name.empty())
				headerdone(name);
		} while (parser.next());
	}

	if (!e.subentities.empty())
	{
		if (decode_subentities)
		{
			for (auto &subentity:e.subentities)
			{
				decode<crlf>(subentity);
			}
		}
		return;
	}

	if (!decode_body)
		return;

	decoding_error=false;

	chunking_decoder chunk{out};

	if (charset.empty() || std::string_view{
			e.content_type.value
		}.substr(0, 5) != "text/")
	{
		if (!e.decode_body(src, chunk))
			decoding_error=true;
	}
	else
	{
		rfc2045::entity::converter converter{chunk};

		auto echarset=e.content_type_charset();
		if (!converter.begin({echarset.begin(), echarset.end()},
				     charset)
		    || !e.decode_body(src,
				      [&]
				      (const char *ptr, size_t n)
				      {
					      converter(ptr, n);
				      }))
		{
			decoding_error=true;
		}

		bool conversion_error;
		converter.end(conversion_error);

		if (conversion_error)
			decoding_error=true;
	}

	if (add_eol)
		chunk(eol.data(), eol.size());

	chunk.flush();
	if (decoding_error && report_decoding_error)
	{
		std::string_view error_message{"[MIME decoding error]"};
		out(error_message.data(), error_message.size());
		out(eol.data(), eol.size());
	}
}

template<typename src_type,
	 typename out_chunk> bool rfc2045::entity::decode_body(
		 src_type &&src,
		 out_chunk &&out
	 ) const
{
	bool errflag=false;

	switch (subentities.empty()
		? content_transfer_encoding

		// We ended up here because of multipart/signed, which we
		// quietly pass along as is.
		: cte::eightbit) {
	case cte::base64:
		{
			rfc2047::base64decoder decoder{
				std::forward<out_chunk>(out)
			};

			if (!decode_body_to(src, decoder, startbody))
				errflag=true;
		}
		break;
	case cte::qp:
		{
			rfc2047::qpdecoder decoder{
				std::forward<out_chunk>(out), false
			};

			if (!decode_body_to(src, decoder, startbody))
				errflag=true;
		}
		break;
	default:
		if (!decode_body_to(src, std::forward<out_chunk>(out),
				    startbody))
			errflag=true;
		break;
	}

	return !errflag;
}

template<typename src_type,
	 typename out_chunk,
	 typename pos_type> bool rfc2045::entity::decode_body_to(
		 src_type &&src,
		 out_chunk &&out,
		 pos_type frompos
	 ) const
{
	char buf[BUFSIZ];

	src.pubseekpos(frompos);

	auto s=endbody-frompos;

	while (s)
	{
		auto n=s;

		if (n > BUFSIZ)
		{
			n=BUFSIZ;
		}

		auto done=src.sgetn(buf, n);

		if (done <= 0)
			return false;
		out(buf, done);
		s -= done;
	}

	return true;
}

template<bool crlf>
template<typename src_type>
bool rfc2045::entity::line_iter<crlf>::try_boundary(
	src_type &&src, std::string boundary, const entity &e
)
{
	// base64-encoded content is never decoded, and will never match
	// a boundary.

	if (e.content_transfer_encoding == cte::base64 ||
	    e.content_transfer_encoding == cte::error)
		return false;

	if (!e.subentities.empty() &&
	    e.content_type.value != "multipart/signed")
	{
		for (auto &subentity:e.subentities)
			if (try_boundary(
				    std::move(src),
				    boundary,
				    subentity))
				return true;
		return false;
	}

	boundary_detector detector{boundary};

	rfc2045::mime_decoder do_decoder{
		[&]
		(const char *ptr, size_t n)
		{
			detector(ptr, n);
		}, src};

	do_decoder.decode_header=true;
	do_decoder.decode(e);

	return detector;
}
template<typename src_type>
std::string rfc2045::entity::new_boundary(src_type &&src, unsigned &counter)
	const
{
	std::string boundary;

	do
	{
		boundary=new_boundary(counter);
	} while( line_iter<false>::try_boundary(
			 std::forward<src_type>(src),
			 boundary,
			 *this));

	return boundary;
}

template<bool crlf>
template<typename out_iter, typename src_type>
void rfc2045::entity::line_iter<crlf>::autoconvert(
	const entity &e, out_iter &closure,
	src_type &src, unsigned &counter, autoconvert_meta &metadata)
{
	if (e.subentities.empty()

	    // If some subentity needs to be rewritten then autoconvert_check
	    // set rewrite_transfer_encoding from its default cte:error value
	    // to cte::8bit, otherwise go into autoconvert_entity in order to
	    // preserve the entire multipart entity, including the boundary
	    // delimiter, and avoid needless work.

	    || (e.multipart() && e.rewrite_transfer_encoding == cte::error))
	{
		autoconvert_entity(e, closure, src, metadata);
		return;
	}

	bool seen_content_type=false;

	bool seen_mime_version=e.mime1;

	if (!e.multipart()) // Must be message/ content type
	{
		headers existing_headers{e, src};

		existing_headers.name_lc=false;
		existing_headers.keep_eol=true;

		do
		{
			auto [name_lc, last_line_is_empty] =
				existing_headers.convert_name_check_empty();

			if (!last_line_is_empty)
			{
				auto current_header=
					existing_headers.current_header();

				current_header=metadata.rwheader(
					e,
					name_lc,
					current_header
				);

				closure(current_header.data(),
					current_header.size());
			}
		} while (existing_headers.next());

		closure(eol.data(), eol.size());

		if (!e.subentities.empty()) // Sanity check
		{
			autoconvert(e.subentities[0], closure, src, counter,
				    metadata);
		}
		return;
	}

	auto new_boundary=e.new_boundary(src, counter);

	// Note: new_boundary() seeks, and headers seeks too, and it must
	// win the seek race.

	headers existing_headers{e, src};

	existing_headers.name_lc=false;
	existing_headers.keep_eol=true;

	std::string new_content_type;

	new_content_type.reserve(
		sizeof("Content-Type: ; boundary=\"\"")
		+ 2*eol.size()
		+ e.content_type.value.size()
		+ new_boundary.size());
	new_content_type="Content-Type: ";

	new_content_type += e.content_type.value;
	new_content_type += "; boundary=\"";
	new_content_type += new_boundary;
	new_content_type += "\"";
	new_content_type += eol;

	do
	{
		auto [name_lc, last_line_is_empty] =
			existing_headers.convert_name_check_empty();

		if (name_lc == "content-type")
		{
			if (seen_content_type)
				continue; // That's funny.

			if (!seen_mime_version)
			{
				seen_mime_version=true;
				closure(default_mime_header.data(),
					default_mime_header.size());
				closure(eol.data(), eol.size());
			}
			closure(new_content_type.data(),
				new_content_type.size());

			seen_content_type=true;
			continue;
		}

		if (!last_line_is_empty)
		{
			auto current_header=existing_headers.current_header();

			if (name_lc == "mime_version")
			{
				seen_mime_version=true;
				current_header=default_mime_header;
			}
			closure(current_header.data(), current_header.size());
		}
	} while (existing_headers.next());

	if (!seen_mime_version)
	{
		seen_mime_version=true;
		closure(default_mime_header.data(),
			default_mime_header.size());
		closure(eol.data(), eol.size());
	}
	if (!seen_content_type)
	{
		closure(new_content_type.data(),
			new_content_type.size());
	}
	std::string separator;

	separator.reserve(400);

	separator += eol;

	for (const auto &line:boundary_msg)
	{
		separator += line;
		separator += eol;
	}

	for (auto &subentity:e.subentities)
	{
		separator += eol;
		separator += "--";
		separator += new_boundary;
		separator += eol;
		closure(separator.data(), separator.size());
		autoconvert(subentity, closure, src, counter, metadata);
		separator.clear();
	}

	separator += eol;
	separator += "--";
	separator += new_boundary;
	separator += "--";
	separator += eol;
	closure(separator.data(), separator.size());
}

#define rfc2045_rfc2045_h_included 1
#include "rfc2045/rfc2045_encode.h"

#endif
