#include <iostream>
#include <tuple>
#include <vector>
#include <sstream>
#include <string_view>
#include <algorithm>
#include "rfc2045/rfc2045.h"
#include "rfc2045/encode.h"

template<bool crlf, typename test_types>
void testrfc2045line_iter_testset(int &testnum, test_types &tests)
{
	for (auto &t:tests)
	{
		++testnum;

		std::string_view s{t.teststr};

		auto b=s.begin();
		auto e=s.end();

		typename rfc2045::entity::line_iter<crlf>::template iter<
			std::string_view::iterator,
			std::string_view::iterator
			>
			iter{b, e};

		std::vector<std::tuple<std::string, size_t>> output;
		rfc2045::entity entity;

		iter.entered_body();
		while (std::holds_alternative<
		       rfc2045::entity_parse_meta::eof_no>(
			       iter.eof()
		       ))
		{
			const auto &[p, q]=iter.current_line();

			std::string s{p, q};
			bool ignore;

			output.emplace_back(
				s,
				iter.consume_line_and_update_position(
					entity,
					rfc2045::cte::eightbit,
					ignore
				)
			);

		}

		if (output != std::vector<std::tuple<std::string, size_t>>{
				t.output.begin(), t.output.end()})

		{
			std::cout << "testrfc2045iter test "
				  << testnum
				  << " failed:\n";

			for (auto &[s, n]:output)
			{
				for (char c:s)
				{
					if (c == '\r')
					{
						std::cout << "\\r";
					}
					else if (c == '\n')
					{
						std::cout << "\\n";
					}
					else
					{
						std::cout << c;
					}
				}

				std::cout << ":" << n << "\n";
			}
			exit(1);
		}
	}
}

static void testrfc2045line_iter()
{
	int testnum=0;

	static const struct {
		const char *teststr;
		std::vector<std::tuple<const char *, size_t>> output;

	} testlf[]={
		{
			"line1\n"
			"line2",
			{
				{"line1", 6},
				{"line2", 5}
			}
		},
		{
			"line1\n",
			{
				{"line1", 6},
			}
		},
	}, testcrlf[]={
		{
			"line3\nline4\r\nline5\r\n",
			{
				{ "line3\nline4", 13 },
				{ "line5", 7},
			}
		},
		{
			"line7",
			{
				{ "line7", 5},
			}
		},
		{
			"line8\r",
			{
				{ "line8\r", 6},
			}
		}
	};
	testrfc2045line_iter_testset<false>(testnum, testlf);
	testrfc2045line_iter_testset<true>(testnum, testcrlf);

	std::string toolarge(static_cast<size_t>(2000),
			     static_cast<char>('a'));

	const struct {
		std::string teststr;
		std::vector<std::tuple<std::string, size_t>> output;

	} testlargeheader1[]={
		{
			std::string{"line10\n" + toolarge + "\n"},
			{
				{"line10", 7},
				{toolarge.substr(0, 1024), 2001},
			}
		},
		{
			std::string{"line11\n" + toolarge + "\n"
				    "nextline\n"},
			{
				{"line11", 7},
				{toolarge.substr(0, 1024), 2001},
				{"nextline", 9},
			}
		},
		{
			std::string{"line12\n" + toolarge},
			{
				{"line12", 7},
				{toolarge.substr(0, 1024), 2000},
			}
		},
	}, testlargeheader2[]={
		{
			std::string{"line13\r\n" + toolarge + "\r\n"},
			{
				{"line13", 8},
				{toolarge.substr(0, 1024), 2002},
			}
		},
		{
			std::string{"line14\r\n" + toolarge + "\r\n"
				    "nextline\r\n"},
			{
				{"line14", 8},
				{toolarge.substr(0, 1024), 2002},
				{"nextline", 10},
			}
		},
		{
			std::string{"line15\r\n" + toolarge},
			{
				{"line15", 8},
				{toolarge.substr(0, 1024), 2000},
			}
		},
	};
	testrfc2045line_iter_testset<false>(testnum, testlargeheader1);
	testrfc2045line_iter_testset<true>(testnum, testlargeheader2);
}

void testrfc2045foldedline_iter()
{
	size_t testnum=0;

	static const struct {
		const char *teststr;
		std::vector<std::tuple<const char *, const char *, const char *,
				       size_t, size_t>> results;
	} tests[] = {
		{
			"",
			{
				{
					"",
					"",
					"", 0, 0
				}
			}
		},
		{
			"Header: online\n",
			{
				{
					"header: online",
					"header",
					"online", 15, 1
				},
				{
					"",
					"",
					"", 15, 1
				}
			}
		},
		{
			"header:onelinenonl",
			{
				{
					"header:onelinenonl",
					"header",
					"onelinenonl", 18, 1
				},
				{
					"",
					"",
					"", 18, 1
				}
			}
		},
		{
			"header1:    line\n\n",
			{
				{
					"header1:    line",
					"header1",
					"line", 17, 1
				},
				{
					"",
					"",
					"", 18, 2
				}
			}
		},
		{
			"Header: line1\n\t\t\tline2\nHeader: line3\n\n",
			{
				{
					"header: line1 line2",
					"header",
					"line1 line2", 23, 2
				},
				{
					"header: line3",
					"header",
					"line3", 37, 3
				},
				{
					"",
					"",
					"", 38, 4
				}
			}
		},
		{
			"Header: line4\n line5\n line6\n",
			{
				{
					"header: line4 line5 line6",
					"header",
					"line4 line5 line6", 28, 3
				},
				{
					"",
					"",
					"", 28, 3
				}
			}
		},
		{
			"Header: line7\n line8\n line9",
			{
				{
					"header: line7 line8 line9",
					"header",
					"line7 line8 line9", 27, 3
				},
				{
					"",
					"",
					"", 27, 3
				}
			}
		}
	};

#if 0
#define UPDATE_FOLDEDLINETEST 1
	const char *sep="";
#endif
	for (const auto &t:tests)
	{
		++testnum;

		std::string_view teststr{t.teststr};

		rfc2045::entity entity;

		auto b=teststr.begin();
		auto e=teststr.end();
		rfc2045::entity::line_iter<false>::iter iter{b, e};
		rfc2045::entity_parse_meta::scope scope{iter, &entity};
		std::string s;

#if UPDATE_FOLDEDLINETEST
		std::cout << sep << "{\n\t\"";
		for (char c:std::string_view{t.teststr})
		{
			switch (c) {
			case '\n':
				std::cout << "\\n";
				break;
			case '\r':
				std::cout << "\\r";
				break;
			case '\t':
				std::cout << "\\t";
				break;
			default:
				std::cout << c;
			}
		}

		std::cout << "\",\n\t{";

		sep="\n";
#else
		std::vector<std::tuple<std::string, std::string, std::string,
				       size_t, size_t>> results;
#endif

		do
		{
			const auto &[name, contents] =
				iter.next_folded_header_line(entity, s);
#if UPDATE_FOLDEDLINETEST
			std::cout << sep << "\t\t{\n\t\t\t\""
				  << s
				  << "\",\n\t\t\t\"" << name
				  << "\",\n\t\t\t\"" << contents
				  << "\", " << entity.endbody
				  << ", " << entity.nlines << "\n\t\t}";
			sep=",\n";
#else
			results.emplace_back(s, name, contents,
					     entity.endbody, entity.nlines);
#endif
		} while (!s.empty());

#if UPDATE_FOLDEDLINETEST
		std::cout << "\n\t}\n}";
		sep=",\n";
#else
		if (results.size() != t.results.size()
		    || !std::equal(results.begin(), results.end(),
				   t.results.begin()))
		{
			std::cout << "testrfc2045foldedline_iter test "
				  << testnum << " failed:\n";

			for (auto &[header, name, value, endbody, nlines]
				     :results)
			{
				std::cout << "\"" << header << "\", \""
					  << name << "\", \""
					  << value << "\", "
					  << endbody << ", " << nlines
					  << "\n";
			}
			exit(1);
		}
#endif
	}
#if UPDATE_FOLDEDLINETEST
	std::cout << "\n";
#endif
}

struct full_parameter_value : rfc2231::header_parameter_value {
	using header_parameter_value::header_parameter_value;

	full_parameter_value(const header_parameter_value &o)
		: header_parameter_value{o}
	{
	}

	bool operator==(const full_parameter_value &o) const
	{
		return header_parameter_value::operator==(o) &&
			index == o.index;
	}
};

void testrfc2231headers()
{
	static const struct {
		const std::string_view header;
		const char *value;
		std::unordered_map<std::string_view,
				   std::tuple<size_t,
					      const char *, const char *,
					      const char *>> parameters;
		std::unordered_map<std::string_view,
				   std::tuple<size_t,
					      const char *, const char *,
					      const char *>> no2231parameters;
	} tests[]={
		{
			"TEXT/PLAIN; "
			"CHARSET=\"iso-8859-1\"",
			"text/plain",
			{
				{"charset", {0, "utf-8", "", "iso-8859-1"}}
			},
			{
				{"charset", {0, "utf-8", "", "iso-8859-1"}}
			}
		},
		{
			"text/html;;novalue;quotedvalue=\"quoted\";",
			"text/html",
			{
				{"novalue", {0, "utf-8", "", ""}},
				{"quotedvalue", {1, "utf-8", "", "quoted"}}
			},
			{
				{"novalue", {0, "utf-8", "", ""}},
				{"quotedvalue", {1, "utf-8", "", "quoted"}}
			}
		},
		{
			"text/plain; "
			"nonstandard1==?utf-8?q?nob=c3=92dy?=; "
			"nonstandard2=\"=?utf-8?q?nob=c3=92dy?=\"; "
			"notrailing=semicolon",
			"text/plain",
			{
				{"nonstandard1", {0, "utf-8", "", "nobÒdy"}},
				{"nonstandard2", {1, "utf-8", "", "nobÒdy"}},
				{"notrailing", {2, "utf-8", "", "semicolon"}}
			},
			{
				{"nonstandard1", {0, "utf-8", "", "=?utf-8?q?nob=c3=92dy?="}},
				{"nonstandard2", {1, "utf-8", "", "=?utf-8?q?nob=c3=92dy?="}},
				{"notrailing", {2, "utf-8", "", "semicolon"}}
			}
		},
		{
			"text/plain; "
			"bells_and_whistles*0*=UTF-8'EN_US'A%6c%6C%20; "
			"bells_and_whistles*1=the%bells; "
			"bells_and_whistles*2*=%20and%20more%2E;",
			"text/plain",
			{
				{"bells_and_whistles", {2, "utf-8", "en_us", "All the%bells and more."}}
			},
			{
				{"bells_and_whistles*0*", {0, "utf-8", "", "UTF-8'EN_US'A%6c%6C%20"}},
				{"bells_and_whistles*1", {1, "utf-8", "", "the%bells"}},
				{"bells_and_whistles*2*", {2, "utf-8", "", "%20and%20more%2E"}}
			}
		}
	};

#if 2

	size_t testnum=0;
#else
	const char *sep="";

#define UPDATE_RFC2231TEST 1
#endif

	for (auto &t:tests)
	{
		rfc2231::header header{t.header, true};
		rfc2231::header noheader{t.header, false};

		std::map<std::string, full_parameter_value> sorted_parameters;

		for (auto &[k, v] : header.parameters)
			sorted_parameters.emplace(k, v);

		std::map<std::string, full_parameter_value> nosorted_parameters;

		for (auto &[k, v] : noheader.parameters)
			nosorted_parameters.emplace(k, v);
#if UPDATE_RFC2231TEST

		std::cout << sep << "{\n\t\"";

		for (char c:t.header)
		{
			if (c == '"' || c == '\\')
				std::cout << "\\";
			std::cout << c;

			if (c == ' ')
			{
				std::cout << "\"\n\t\"";
			}
		}

		std::cout << "\",\n\t\"" << header.value << "\",\n"
			"\t{";

		sep="\n";

		for (auto &[paramname, paramvalue] : sorted_parameters)
		{
			std::cout << sep << "\t\t{\""
				  << paramname << "\", {"
				  << paramvalue.index << ", \""
				  << paramvalue.charset << "\", \""
				  << paramvalue.language << "\", \""
				  << paramvalue.value << "\"}}";
			sep=",\n";
		}

		std::cout << "\n\t},\n\t{";
		sep="\n";

		for (auto &[paramname, paramvalue] : nosorted_parameters)
		{
			std::cout << sep << "\t\t{\""
				  << paramname << "\", {"
				  << paramvalue.index << ", \""
				  << paramvalue.charset << "\", \""
				  << paramvalue.language << "\", \""
				  << paramvalue.value << "\"}}";
			sep=",\n";
		}
		std::cout << "\n\t}\n}";
#else
		std::map<std::string, full_parameter_value
			 > expected_sorted_parameters,
			expected_nosorted_parameters;

		for (auto &[name, value] : t.parameters)
		{
			auto &[index, charset, language, contents] = value;

			expected_sorted_parameters.emplace(
				std::piecewise_construct,
				std::forward_as_tuple(name),
				std::forward_as_tuple(index,
						      charset,
						      language,
						      contents));
		}

		for (auto &[name, value] : t.no2231parameters)
		{
			auto &[index, charset, language, contents] = value;

			expected_nosorted_parameters.emplace(
				std::piecewise_construct,
				std::forward_as_tuple(name),
				std::forward_as_tuple(index,
						      charset,
						      language,
						      contents));
		}

		++testnum;
		if (header.value != t.value ||
		    sorted_parameters != expected_sorted_parameters ||
		    nosorted_parameters != expected_nosorted_parameters)
		{
			std::cout << "rfc2231 test " << testnum
				  << " failed:\n";
			std::cout << header.value << "\n";

			for (auto &[paramname, paramvalue] : sorted_parameters)
			{
				std::cout << "    " << paramname << ": "
					  << paramvalue.index << ", "
					  << paramvalue.charset << ", "
					  << paramvalue.language << ": "
					  << paramvalue.value << "\n";
			}

			std::cout << "----------------\n";

			for (auto &[paramname, paramvalue]
				     : nosorted_parameters)
			{
				std::cout << "    " << paramname << ": "
					  << paramvalue.index << ", "
					  << paramvalue.charset << ", "
					  << paramvalue.language << ": "
					  << paramvalue.value << "\n";
			}
			exit(1);
		}
#endif
	}

#if UPDATE_RFC2231TEST
	std::cout << "\n";
#endif
}

void testrfc2045foldedline_iter2()
{
	std::string test_str="Header: begin@!end\n";

	std::replace(test_str.begin(), test_str.end(), '@', '\0');
	std::replace(test_str.begin(), test_str.end(), '!', '\r');

	rfc2045::entity entity;

	auto b=test_str.begin();
	auto e=test_str.end();
	rfc2045::entity::line_iter<false>::iter iter{b, e};
	rfc2045::entity_parse_meta::scope scope{iter, &entity};

	std::string s;

	const auto &[name, contents] =
		iter.next_folded_header_line(entity, s);

	if (contents != "beginend")
	{
		std::cout << "testrfc2045foldedline_iter2 test failed: "
			  << contents << "\n";
		exit(1);
	}
}
#if 0
#define UPDATE_TESTSUITECPP 1
#endif

#include "testsuitecpp.H"

void testmimeparse()
{
	using cte=rfc2045::cte;

	static const struct {
		const char *message;

		parsed_mime_info parsed;
	} tests1[]={
		// Test 1
		{
			"",
			0    , // startpos
			0    , // startbody
			0    , // endbody
			0    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 2
		{
			"\n",
			0    , // startpos
			1    , // startbody
			1    , // endbody
			1    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 3
		{
			"Subject: something\n"
			"\n",
			0    , // startpos
			20   , // startbody
			20   , // endbody
			2    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 4
		{
			"Subject: something\n",
			0    , // startpos
			19   , // startbody
			19   , // endbody
			1    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 5
		{
			"Subject: something",
			0    , // startpos
			18   , // startbody
			18   , // endbody
			1    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 6
		{
			"Subject: something\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=\"UTF-8\"\n"
			"\n"
			"Content-Type: text/plain\n",
			0    , // startpos
			80   , // startbody
			105  , // endbody
			5    , // nlines
			1    , // nbodylines
			1    , // mime1
			0, "text/plain", "utf-8",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 7
		{
			"Subject: msg\n"
			"Mime-Version: 1.0\n"
			"Content-Type: message/rfc822\n"
			"\n"
			"Subject: inner msg\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8BIT\n"
			"\n"
			"Hello\n",
			0    , // startpos
			61   , // startbody
			177  , // endbody
			10   , // nlines
			6    , // nbodylines
			1    , // mime1
			0, "message/rfc822", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					61   , // startpos
					171  , // startbody
					177  , // endbody
					6    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/plain", "utf-8",
					"", cte::eightbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 8
		{
			"Subject: multipart\n"
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aa\n"
			"\n"
			"Preamble\n"
			"--aa\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"\n"
			"text\n"
			"\n"
			"--aasuffix\n"
			"Content-Type: text/html; charset='iso-8859-1\n"
			"\n"
			"html\n"
			"\n"
			"--aa--\n"
			"Postamble\n",
			0    , // startpos
			81   , // startbody
			227  , // endbody
			17   , // nlines
			13   , // nbodylines
			1    , // mime1
			0, "multipart/mixed", "iso-8859-1",
			"aa", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					95   , // startpos
					141  , // startbody
					146  , // endbody
					3    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/plain", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				},
				{
					158  , // startpos
					204  , // startbody
					209  , // endbody
					3    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/html", "'iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 9
		{
			"Subject: multipart\n"
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aa\n"
			"\n"
			"--aa\n"
			"Content-Type: multipart/alternative; boundary=ab\n"
			"\n"
			"--ab\n"
			"Content-Type: text/html\n"
			"\n"
			"html\n"
			"--ab\n"
			"Content-Type: text/plain\n"
			"\n"
			"plain\n"
			"\n"
			"--ab--\n"
			"--aa\n"
			"Content-Type: text/csv\n"
			"\n"
			"a,b,c\n"
			"\n"
			"--aa--\n",
			0    , // startpos
			81   , // startbody
			259  , // endbody
			23   , // nlines
			19   , // nbodylines
			1    , // mime1
			0, "multipart/mixed", "iso-8859-1",
			"aa", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					86   , // startpos
					136  , // startbody
					215  , // endbody
					12   , // nlines
					10   , // nbodylines
					1    , // mime1
					0, "multipart/alternative", "iso-8859-1",
					"ab", cte::eightbit,
					0, // has8bitheader
					0, // has8bitbody
					0, // has8bitcontentchar
					{
						{
							141  , // startpos
							166  , // startbody
							170  , // endbody
							3    , // nlines
							1    , // nbodylines
							1    , // mime1
							0, "text/html", "iso-8859-1",
							"", cte::sevenbit,
							0, // has8bitheader
							0, // has8bitbody
							0  // has8bitcontentchar
						},
						{
							176  , // startpos
							202  , // startbody
							208  , // endbody
							3    , // nlines
							1    , // nbodylines
							1    , // mime1
							0, "text/plain", "iso-8859-1",
							"", cte::sevenbit,
							0, // has8bitheader
							0, // has8bitbody
							0  // has8bitcontentchar
						}
					}
				},
				{
					221  , // startpos
					245  , // startbody
					251  , // endbody
					3    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/csv", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 10
		{
			"Subject: messages\n"
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/digest; boundary=xxx\n"
			"\n"
			"--xxx\n"
			"\n"
			"Subject: one\n"
			"\n"
			"one\n"
			"\n"
			"--xxx\n"
			"\n"
			"Subject: two\n"
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/alternative; boundary=yyy\n"
			"\n"
			"Preamble\n"
			"--yyyy\n"
			"Content-Type: text/html\n"
			"\n"
			"html\n"
			"\n"
			"--yyy\n"
			"Content-Type: text/plain\n"
			"\n"
			"plain\n"
			"--yyy--\n"
			"--xxx\n"
			"\n"
			"Subject: three\n"
			"\n"
			"Three\n"
			"\n"
			"--xxx--\n",
			0    , // startpos
			82   , // startbody
			328  , // endbody
			34   , // nlines
			30   , // nbodylines
			1    , // mime1
			0, "multipart/digest", "iso-8859-1",
			"xxx", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					88   , // startpos
					89   , // startbody
					107  , // endbody
					4    , // nlines
					3    , // nbodylines
					1    , // mime1
					0, "message/rfc822", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0, // has8bitcontentchar
					{
						{
							89   , // startpos
							103  , // startbody
							107  , // endbody
							3    , // nlines
							1    , // nbodylines
							0    , // mime1
							0, "text/plain", "iso-8859-1",
							"", cte::sevenbit,
							0, // has8bitheader
							0, // has8bitbody
							0  // has8bitcontentchar
						}
					}
				},
				{
					114  , // startpos
					115  , // startbody
					289  , // endbody
					16   , // nlines
					15   , // nbodylines
					1    , // mime1
					0, "message/rfc822", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0, // has8bitcontentchar
					{
						{
							115  , // startpos
							197  , // startbody
							289  , // endbody
							15   , // nlines
							11   , // nbodylines
							1    , // mime1
							0, "multipart/alternative", "iso-8859-1",
							"yyy", cte::eightbit,
							0, // has8bitheader
							0, // has8bitbody
							0, // has8bitcontentchar
							{
								{
									213  , // startpos
									238  , // startbody
									243  , // endbody
									3    , // nlines
									1    , // nbodylines
									1    , // mime1
									0, "text/html", "iso-8859-1",
									"", cte::sevenbit,
									0, // has8bitheader
									0, // has8bitbody
									0  // has8bitcontentchar
								},
								{
									250  , // startpos
									276  , // startbody
									281  , // endbody
									3    , // nlines
									1    , // nbodylines
									1    , // mime1
									0, "text/plain", "iso-8859-1",
									"", cte::sevenbit,
									0, // has8bitheader
									0, // has8bitbody
									0  // has8bitcontentchar
								}
							}
						}
					}
				},
				{
					296  , // startpos
					297  , // startbody
					319  , // endbody
					4    , // nlines
					3    , // nbodylines
					1    , // mime1
					0, "message/rfc822", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0, // has8bitcontentchar
					{
						{
							297  , // startpos
							313  , // startbody
							319  , // endbody
							3    , // nlines
							1    , // nbodylines
							0    , // mime1
							0, "text/plain", "iso-8859-1",
							"", cte::sevenbit,
							0, // has8bitheader
							0, // has8bitbody
							0  // has8bitcontentchar
						}
					}
				}
			}
		},

		// Test 11
		{
			"Mime-Version: 1.0\n"
			"Content-Type: message/rfc822\n"
			"\n"
			"Subject: испытание\n"
			"\n",
			0    , // startpos
			48   , // startbody
			77   , // endbody
			5    , // nlines
			2    , // nbodylines
			1    , // mime1
			0, "message/rfc822", "iso-8859-1",
			"", cte::eightbit,
			0, // has8bitheader
			1, // has8bitbody
			1, // has8bitcontentchar
			{
				{
					48   , // startpos
					77   , // startbody
					77   , // endbody
					2    , // nlines
					0    , // nbodylines
					0    , // mime1
					RFC2045_ERR8BITHEADER, "text/plain", "iso-8859-1",
					"", cte::sevenbit,
					1, // has8bitheader
					0, // has8bitbody
					1  // has8bitcontentchar
				}
			}
		},

		// Test 12
		{
			"Mime-Version: 1.0\n"
			"Content-Type: message/rfc822\n"
			"\n"
			"Subject: test\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"испытание\n",
			0    , // startpos
			48   , // startbody
			172  , // endbody
			9    , // nlines
			6    , // nbodylines
			1    , // mime1
			0, "message/rfc822", "iso-8859-1",
			"", cte::eightbit,
			0, // has8bitheader
			1, // has8bitbody
			1, // has8bitcontentchar
			{
				{
					48   , // startpos
					153  , // startbody
					172  , // endbody
					6    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/plain", "utf-8",
					"", cte::eightbit,
					0, // has8bitheader
					1, // has8bitbody
					1  // has8bitcontentchar
				}
			}
		},

		// Test 13
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=AAA\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain\n"
			"Subject: испытание\n"
			"\n"
			"Test\n"
			"\n"
			"--aaa--\n",
			0    , // startpos
			63   , // startbody
			137  , // endbody
			10   , // nlines
			7    , // nbodylines
			1    , // mime1
			0, "multipart/mixed", "iso-8859-1",
			"aaa", cte::eightbit,
			0, // has8bitheader
			1, // has8bitbody
			1, // has8bitcontentchar
			{
				{
					69   , // startpos
					123  , // startbody
					128  , // endbody
					4    , // nlines
					1    , // nbodylines
					1    , // mime1
					RFC2045_ERR8BITHEADER, "text/plain", "iso-8859-1",
					"", cte::sevenbit,
					1, // has8bitheader
					0, // has8bitbody
					1  // has8bitcontentchar
				}
			}
		},

		// Test 14
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--AAA\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"испытание\n"
			"Test\n"
			"\n"
			"--aaa--\n",
			0    , // startpos
			63   , // startbody
			175  , // endbody
			11   , // nlines
			8    , // nbodylines
			1    , // mime1
			0, "multipart/mixed", "iso-8859-1",
			"aaa", cte::eightbit,
			0, // has8bitheader
			1, // has8bitbody
			1, // has8bitcontentchar
			{
				{
					69   , // startpos
					142  , // startbody
					166  , // endbody
					5    , // nlines
					2    , // nbodylines
					1    , // mime1
					0, "text/plain", "utf-8",
					"", cte::eightbit,
					0, // has8bitheader
					1, // has8bitbody
					1  // has8bitcontentchar
				}
			}
		},

		// Test 15
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"=A0=20=ff=FF\n",
			0    , // startpos
			108  , // startbody
			121  , // endbody
			5    , // nlines
			1    , // nbodylines
			1    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::qp,
			0, // has8bitheader
			0, // has8bitbody
			1  // has8bitcontentchar
		},

		// Test 16
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"и\n",
			0    , // startpos
			108  , // startbody
			111  , // endbody
			5    , // nlines
			1    , // nbodylines
			1    , // mime1
			RFC2045_ERR8BITINQP, "text/plain", "iso-8859-1",
			"", cte::qp,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 17
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"=ZZ\n",
			0    , // startpos
			108  , // startbody
			112  , // endbody
			5    , // nlines
			1    , // nbodylines
			1    , // mime1
			RFC2045_ERRBADHEXINQP, "text/plain", "iso-8859-1",
			"", cte::qp,
			0, // has8bitheader
			0, // has8bitbody
			1  // has8bitcontentchar
		},

		// Test 18
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"\n"
			"и",
			0    , // startpos
			59   , // startbody
			61   , // endbody
			4    , // nlines
			1    , // nbodylines
			1    , // mime1
			RFC2045_ERR8BITCONTENT, "text/plain", "utf-8",
			"", cte::sevenbit,
			0, // has8bitheader
			1, // has8bitbody
			1  // has8bitcontentchar
		},

		// Test 19
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed\n"
			"Test\n",
			0    , // startpos
			53   , // startbody
			53   , // endbody
			3    , // nlines
			0    , // nbodylines
			1    , // mime1
			RFC2045_ERRBADBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 20
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=AA\n"
			"\n"
			"\n"
			"--AA\n"
			"Content-Type: multipart/mixed; boundary=AA1\n"
			"\n"
			"--AA1\n"
			"Content-Type: text/plain\n"
			"\n"
			"Test\n"
			"\n"
			"--AA1--\n"
			"--AA--\n",
			0    , // startpos
			62   , // startbody
			113  , // endbody
			7    , // nlines
			4    , // nbodylines
			1    , // mime1
			RFC2045_ERRBADBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
			"aa", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					68   , // startpos
					113  , // startbody
					113  , // endbody
					2    , // nlines
					0    , // nbodylines
					1    , // mime1
					RFC2045_ERRBADBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
					"aa1", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 21
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=AA11\n"
			"\n"
			"\n"
			"--AA11\n"
			"Content-Type: multipart/mixed; boundary=AA1\n"
			"\n"
			"--AA1\n"
			"Content-Type: text/plain\n"
			"\n"
			"Test\n"
			"\n"
			"--AA1--\n"
			"--AA11--\n",
			0    , // startpos
			64   , // startbody
			117  , // endbody
			7    , // nlines
			4    , // nbodylines
			1    , // mime1
			RFC2045_ERRBADBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
			"aa11", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					72   , // startpos
					117  , // startbody
					117  , // endbody
					2    , // nlines
					0    , // nbodylines
					1    , // mime1
					RFC2045_ERRBADBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
					"aa1", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 22
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=AA11\n"
			"\n"
			"\n"
			"--AA11\n"
			"Content-Type: multipart/mixed; boundary=AA12\n"
			"\n"
			"--AA11\n"
			"Content-Type: text/plain\n"
			"\n"
			"Test\n"
			"\n"
			"--AA12--\n"
			"--AA11--\n",
			0    , // startpos
			64   , // startbody
			118  , // endbody
			7    , // nlines
			4    , // nbodylines
			1    , // mime1
			RFC2045_ERRWRONGBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
			"aa11", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					72   , // startpos
					118  , // startbody
					118  , // endbody
					2    , // nlines
					0    , // nbodylines
					1    , // mime1
					RFC2045_ERRWRONGBOUNDARY|RFC2045_ERRFATAL, "multipart/mixed", "iso-8859-1",
					"aa12", cte::eightbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 23
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=\"jack-1759500593-0000\"\n"
			"\n"
			"\n"
			"--jack-1759500593-0000\n"
			"Content-Type: text/plain\n"
			"\n"
			"\n"
			"\n"
			"--jack-1759500593-0000--\n"
			"\n",
			0    , // startpos
			82   , // startbody
			160  , // endbody
			11   , // nlines
			8    , // nbodylines
			1    , // mime1
			0, "multipart/mixed", "iso-8859-1",
			"jack-1759500593-0000", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0, // has8bitcontentchar
			{
				{
					106  , // startpos
					132  , // startbody
					133  , // endbody
					3    , // nlines
					1    , // nbodylines
					1    , // mime1
					0, "text/plain", "iso-8859-1",
					"", cte::sevenbit,
					0, // has8bitheader
					0, // has8bitbody
					0  // has8bitcontentchar
				}
			}
		},

		// Test 24
		{
			"Content-Type: multipart\n"
			"Subject: Ignoring content-type without mime-version\n"
			"\n"
			"Hello world!\n",
			0    , // startpos
			77   , // startbody
			90   , // endbody
			4    , // nlines
			1    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 25
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain\n"
			"Content-Type: text/plain\n"
			"\n",
			0    , // startpos
			69   , // startbody
			69   , // endbody
			4    , // nlines
			0    , // nbodylines
			1    , // mime1
			RFC2045_ERRDUPLICATECONTENT|RFC2045_ERRFATAL, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 26
		{
			"Mime-Version: 1.0\n"
			"Content-Transfer-Encoding: 8bit\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n",
			0    , // startpos
			83   , // startbody
			83   , // endbody
			4    , // nlines
			0    , // nbodylines
			1    , // mime1
			RFC2045_ERRDUPLICATECONTENT|RFC2045_ERRFATAL, "text/plain", "iso-8859-1",
			"", cte::eightbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 27
		{
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"\n",
			0    , // startpos
			81   , // startbody
			81   , // endbody
			3    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		},

		// Test 28
		{
			"Content-Transfer-Encoding: 8bit\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n",
			0    , // startpos
			65   , // startbody
			65   , // endbody
			3    , // nlines
			0    , // nbodylines
			0    , // mime1
			0, "text/plain", "iso-8859-1",
			"", cte::sevenbit,
			0, // has8bitheader
			0, // has8bitbody
			0  // has8bitcontentchar
		}
	};

#if UPDATE_TESTSUITECPP
	const char *sep="";
#endif

	size_t testnum=0;

	for (const auto &t:tests1)
	{
		++testnum;
		rfc2045::entity entity;

		std::string_view message{t.message};
		auto b=message.begin(), e=message.end();

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

		entity.parse(parser);

#if UPDATE_TESTSUITECPP
		b=message.begin(); e=message.end();
		std::cout << sep << "\n// Test " << testnum << "\n{\n\t\"";

		const char *end_sep="\"";
		sep="";
		while (b != e)
		{
			auto p=b;
			b=std::find(b, e, '\n');

			std::cout << sep;

			for (char c:std::string_view(p, b-p))
			{
				if (c == '\\' || c == '"')
					std::cout << '\\';

				std::cout << c;
			}

			if (b != e)
			{
				++b;
				std::cout << "\\n";
			}
			std::cout << "\"";
			sep="\n\t\"";
			end_sep="";
		}

		std::cout << end_sep << ",\n";

		parsed_mime_info::dump_entity(entity, "\t");

		std::cout << "}";
		sep=",\n";
#else
		if (t.parsed != entity)
		{
			std::cout << "Simple mime parsing test " << testnum
				  << " failed:\n";

			t.parsed.dump(entity);
			exit(1);
		}
#endif
	}
#if UPDATE_TESTSUITECPP
	std::cout << "\n";
#endif

#define LONGQUOTEDCHUNKSIZE	20
#define LONGQUOTEDLINESIZE	1020 // Must be multiple of 20

	static const struct {
		const char *prefix;
		const char *repeat;
		size_t nrepeats;
		const char *suffix;
		bool haslongquotedline;

	} tests2[]={

		// Test 100
		{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain\n"
			"\n",
			"12345678901234567890\n", LONGQUOTEDLINESIZE / 20,
			"",
			0
		},

		// Test 101
		{
			"Mime-Version: 1.0\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n",
			"12345678901234567890=\n", LONGQUOTEDLINESIZE / 20 - 1,
			"12345678901234567890\n",
			0
		},

		// Test 102
		{
			"Mime-Version: 1.0\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n",
			"12345678901234567890=\n", LONGQUOTEDLINESIZE / 20 - 1,
			"1234567890123456778901\n",
			1
		},

		// Test 103
		{
			"Mime-Version: 1.0\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n",
			"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20", LONGQUOTEDLINESIZE / 20,
			"\n",
			0
		},

		// Test 104
		{
			"Mime-Version: 1.0\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n",
			"=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20", LONGQUOTEDLINESIZE / 20 + 1,
			"\n",
			1
		}
	};

#if 0
	const char *sep="";

#define UPDATE_MIMELONGLINE 1
#endif

	testnum=99;

	for (const auto &t:tests2)
	{
		++testnum;
		rfc2045::entity entity;

		std::string message;

		std::string_view prefix{t.prefix};
		std::string_view repeat{t.repeat};
		std::string_view suffix{t.suffix};

		message.reserve(prefix.size() +
				repeat.size()*t.nrepeats +
				suffix.size());
		message=prefix;
		for (size_t i=0; i<t.nrepeats; ++i)
			message += repeat;
		message += suffix;

		auto b=message.data();
		auto e=b+message.size();
		rfc2045::entity::line_iter<false>::iter parser{b, e};

		parser.longquotedlinesize=LONGQUOTEDLINESIZE;

		entity.parse(parser);

#if UPDATE_MIMELONGLINE
		std::cout << sep << "\n/" "/ Test " << testnum << "\n{\n";

		for (const auto &stropt: std::array<
			     std::tuple<std::string_view,
			     std::optional<size_t>>, 3>{
			     {
				     { t.prefix, std::nullopt },
				     { t.repeat, t.nrepeats },
				     { t.suffix, std::nullopt}
			     }
		     })
		{
			const auto &[str, opt] = stropt;

			auto b=str.begin(), e=str.end();

			const char *str_sep="";

			std::cout << "\t\"";
			while (b != e)
			{
				std::cout << str_sep;

				auto p=b;
				b=std::find(b, e, '\n');

				for (char c:std::string_view(p, b-p))
				{
					if (c == '\\' || c == '"')
						std::cout << '\\';
					std::cout << c;
				}

				if (b != e)
				{
					++b;
					std::cout << "\\n";
				}
				str_sep="\"\n\t\"";
			}
			std::cout << "\",";
			if (opt)
			{
				std::cout << " LONGQUOTEDLINESIZE / "
					  << LONGQUOTEDCHUNKSIZE;

				if (LONGQUOTEDLINESIZE / LONGQUOTEDCHUNKSIZE
				    != *opt)
				{
					if (LONGQUOTEDLINESIZE /
					    LONGQUOTEDCHUNKSIZE < *opt)
						std::cout << " + " <<
							*opt
							- LONGQUOTEDLINESIZE /
							LONGQUOTEDCHUNKSIZE;
					else
						std::cout << " - " <<
							(LONGQUOTEDLINESIZE /
							 LONGQUOTEDCHUNKSIZE
							 - *opt);
				}
				std::cout << ",";
			}
			std::cout << "\n";
		}
		std::cout << "\t" << entity.haslongquotedline << "\n}";
		sep=",\n";
#else
		if ((entity.errors.code & RFC2045_ERRLONGQUOTEDPRINTABLE ?
		     true:false)
		    != t.haslongquotedline)
		{
			std::cout << "Test " << testnum << ":\n";
			std::cout << "   haslongquotedline: "
				  << (entity.errors.code &
				      RFC2045_ERRLONGQUOTEDPRINTABLE ? 1:0)
				  << "\n";
			exit(1);
		}
#endif
	}

#if UPDATE_MIMELONGLINE
	std::cout << "\n";
#endif

	{
		std::string message{
			"Subject: test\n"
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=\"aaa\"\n"
			"Content-Transfer-Encoding: base64\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain\n"
			"\n"
			"--aaa--\n"
		};

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity entity;
		rfc2045::entity::line_iter<false>::iter parser{b, e};
		entity.parse(parser);

		if (!(entity.errors.code & RFC2045_ERRINVALIDBASE64))
		{
			std::cout << "bad base64 test 1 failed\n";
			exit(1);
		}
	}

	{
		std::string message{
			"Subject: test\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain\n"
			"Content-Transfer-Encoding: base64\n"
			"\n"
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"
			" \n"
		};

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity entity;
		rfc2045::entity::line_iter<false>::iter parser{b, e};
		entity.parse(parser);

		if (entity.errors.code)
		{
			std::cout << "base base64 test 2 failed\n";
			exit(1);
		}
	}

	{
		std::string message{
			"Subject: test\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain\n"
			"Content-Transfer-Encoding: base64\n"
			"\n"
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=~\n"
			" \t\r\n"
		};

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity entity;
		rfc2045::entity::line_iter<false>::iter parser{b, e};
		entity.parse(parser);

		if (!(entity.errors.code & RFC2045_ERRINVALIDBASE64))
		{
			std::cout << "bad base64 test 3 failed\n";
			exit(1);
		}
	}

	{
		std::string message{
			"Subject: test\n"
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain\n"
			"Content-Transfer-Encoding: base64\n"
			"\n"
		};

		message += std::string(2000, ' ');

		message +=
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=~\n"
			" \t\r\n";

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity entity;
		rfc2045::entity::line_iter<false>::iter parser{b, e};
		entity.parse(parser);

		if (!(entity.errors.code & RFC2045_ERRINVALIDBASE64))
		{
			std::cout << "bad base64 test 4 failed\n";
			exit(1);
		}
	}
}

void testmimelimits()
{
	std::string message{
		"Subject: test\n"
		"Mime-Version: 1.0\n"
		"Content-Type: multipart/mixed; boundary=\"aaa\"\n"
		"\n"
	};

	for (int i=0; i<10; i++)
	{
		message += "\n--aaa\n"
			"Content-Type: text/plain\n\n\n";
	}

	message += "\n--aaa--\n";

	// There are 11 mime entities.

	{
		rfc2045::entity entity;

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity::line_iter<false>::iter parser{b, e};
		parser.mimeentityparselimit=11;
		entity.parse(parser);

		if (entity.errors.code)
		{
			std::cout << "limit test 1 failed\n";
			exit(1);
		}
	}

	{
		rfc2045::entity entity;

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity::line_iter<false>::iter parser{b, e};
		parser.mimeentityparselimit=10;
		entity.parse(parser);

		if (!(entity.errors.code & RFC2045_ERR2COMPLEX))
		{
			std::cout << "limit test 2 failed\n";
			exit(1);
		}
	}

	// Create 10 nested MIME entities, for a total of 11 entities
	message="Content-Type: text/plain\n\nTest\n";

	for (int i=0; i<10; ++i)
	{
		char boundary[4];

		boundary[0]='A';
		boundary[1]='A';
		boundary[2]='A'+i;
		boundary[3]=0;

		message =
			std::string{"Content-Type: multipart/mixed; boundary="}
			+ boundary + "\n"
				"\n--" + boundary + "\n" + message
				+ "\n--" + boundary + "--\n";
	}

	message="Mime-Version: 1.0\n" + message;

	{
		rfc2045::entity entity;

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity::line_iter<false>::iter parser{b, e};
		parser.mimeentitynestedlimit=11;
		entity.parse(parser);

		if (entity.errors.code)
		{
			std::cout << "limit test 3 failed\n";
			exit(1);
		}
	}

	{
		rfc2045::entity entity;

		auto b=message.begin();
		auto e=message.end();

		rfc2045::entity::line_iter<false>::iter parser{b, e};
		parser.mimeentitynestedlimit=10;
		entity.parse(parser);

		if (!(entity.errors.code & RFC2045_ERR2COMPLEX))
		{
			std::cout << "limit test 4 failed\n";
			exit(1);
		}
	}

	for (size_t s=50; s<52; ++s)
	{
		message="Mime-Version: 1.0\n"

			// The two spaces is intentional
			"Content-Transfer-Encoding:  quoted-printable\n"
			"\n"
			"=20=20=20=20=20=20=20=20=20=20"
			+ std::string(15, ' ') + "=\n"
			+ std::string(s-25, ' ')+"\n";

		auto b=message.begin();
		auto e=message.end();

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

		parser.longquotedlinesize=50;

		rfc2045::entity entity;
		entity.parse(parser);

		if ((entity.errors.code & RFC2045_ERRLONGQUOTEDPRINTABLE ? 1:0)
		    != s-50)
		{
			std::cout << "limit test 5, part "
				  << s-49 << ", failed\n";
			exit(1);
		}
	}

	message="Subject: ";

	message += std::string(16, '1')+"\n";
	message += "    " + std::string(50-1-25, '1')+"\n";
	message += "Mime-Version: 1.0\n"
		"\nTest\n";

	for (size_t s=49; s<51; ++s)
	{
		auto b=message.begin();
		auto e=message.end();

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

		parser.longunfoldedheadersize=s;

		rfc2045::entity entity;
		entity.parse(parser);

		if ( (entity.errors.code & RFC2045_ERRLONGUNFOLDEDHEADER)
		     != (s <= 50 ? RFC2045_ERRLONGUNFOLDEDHEADER:0))
		{
			std::cout << "limit test 6, part "
				  << s-48 << ", failed\n";
			exit(1);
		}
	}
}

void testtryboundary()
{
	rfc2045::entity e;
	std::stringstream message;

	message <<
		"Mime-Version: 1.0\n"
		"Content-Type: multipart/mixed; boundary=bbb\n"
		"\n"
		"--bbb\n"
		"Content-Type: text/plain; charset=iso-8859-1\n"
		"Content-Transfer-Encoding: quoted-printable\n"
		"\n"
		"--=61=61=61\n"
		"\n"
		"--bbb--\n";

	message.rdbuf()->pubseekpos(0);
	{
		std::istreambuf_iterator<char> biter{message}, eiter;
		rfc2045::entity::line_iter<false>::iter parser{biter, eiter};

		e.parse(parser);
	}

	if (e.subentities.size() != 1)
	{
		std::cout << "testtryboundary: MIME parse failed\n";
		exit(1);
	}

	if (!rfc2045::entity::line_iter<false>::try_boundary(
		    *message.rdbuf(),
		    "AA",
		    e))
	{
		std::cout << "try_boundary test failed\n";
		exit(1);
	}
}

void testautoconvert_check()
{
	rfc2045::entity entity;

#define CHARS10 "1234567890"
#define CHARS60 CHARS10 CHARS10 CHARS10 CHARS10 CHARS10 CHARS10
#define CHARS120 CHARS60 CHARS60


	{
		std::string_view message{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain\n"
			"\n"
			CHARS120 "\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			CHARS60 "=\n"
			CHARS60 "\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"12345Ā\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"123=A0\n"
			"\n"
			"--aaa--\n"
			"Hiyo!"
		};

		auto b=message.begin();
		auto e=message.end();

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

		parser.longquotedlinesize=100;

		entity.parse(parser);
	}

	std::vector<std::tuple<bool, bool, bool>> results;

	results.reserve(entity.subentities.size());

	for (auto &e:entity.subentities)
		results.emplace_back(e.has8bitchars,
				     e.hasraw8bitchars,
				     e.haslongline);
	bool error=false;

#if 1
	if (results !=
	    std::vector<std::tuple<bool, bool, bool>>{{
			    {
				    false,	// has8bitchars
				    false,	// hasraw8bitchars
				    true	// haslongline
			    },
			    {
				    false,	// has8bitchars
				    false,	// hasraw8bitchars
				    true	// haslongline
			    },
			    {
				    false,	// has8bitchars
				    true,	// hasraw8bitchars
				    false	// haslongline
			    },
			    {
				    true,	// has8bitchars
				    true,	// hasraw8bitchars
				    false	// haslongline
			    }
		    }})
	{
		error=true;
		std::cerr << "autoconvert test 1 failed:\n\n";
#if 0
	}
#endif
#else
	{
#endif
		for (auto &res:results)
		{
			auto &[has8bitchars, hasraw8bitchars, haslongline]
				= res;

			std::cout << "{\n"
				"\t" << (has8bitchars ? "true":"false")
				  << ",\t// has8bitchars\n"
				"\t" << (hasraw8bitchars ? "true":"false")
				  << ",\t// hasraw8bitchars\n"
				"\t" << (haslongline ? "true":"false")
				  << "\t// haslongline\n},\n";
		}
	}

	if (error)
		exit(1);

	const struct {
		std::string_view message;
		bool has8bitchars;
		bool hasraw8bitchars;
	} tests[]={
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"12345Ā\n"
			"\n"
			"--aaa--\n",

			false,
			true
		},
		{
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=iso-8859-1\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"=A0\n"
			"\n"
			"--aaa--\n",
			true,
			true
		},
	};

	for (const auto &test:tests)
	{
		auto b=test.message.begin();
		auto e=test.message.end();

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

		parser.longquotedlinesize=100;

		entity={};
		entity.parse(parser);

		entity.autoconvert_check(rfc2045::convert::eightbit);

		if (entity.has8bitchars != test.has8bitchars ||
		    entity.hasraw8bitchars != test.hasraw8bitchars)
		{
			std::cout << "autoconvert test 2 failed\n";
			exit(1);
		}
	}

	const struct test3_info {
		const char *test_name;
		std::string_view message;
		bool rewrite7;
		rfc2045::cte rewrite7_to;
		bool rewrite8;
		rfc2045::cte rewrite8_to;
		bool rewritealways;
		rfc2045::cte rewritealways_to;
		bool rewritedefault;
		rfc2045::cte rewritedefault_to;
	} tests3[]={
		{
			"no mime",
			"Subject: test\n"
			"\n"
			"test\n",
			0,
			rfc2045::cte::error,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			0,
			rfc2045::cte::error,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"no mime (subentity)",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"\n"
			"Hello\n"
			"--aaa--\n",
			1,
			rfc2045::cte::error,	// rewrite7
			1,
			rfc2045::cte::error,	// rewrite8
			1,
			rfc2045::cte::error,	// rewritealways
			1,
			rfc2045::cte::error	// standardize
		},
		{
			"no content headers",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"\n"
			"HelloĀ\n"
			"--aaa--\n",
			1,
			rfc2045::cte::qp,	// rewrite7
			1,
			rfc2045::cte::error,	// rewrite8
			1,
			rfc2045::cte::error,	// rewritealways
			1,
			rfc2045::cte::error	// standardize
		},
		{
			"missing charset, encoding",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain\n"
			"\n"
			"HelloĀ\n"
			"--aaa--\n",
			1,
			rfc2045::cte::qp,	// rewrite7
			1,
			rfc2045::cte::error,	// rewrite8
			1,
			rfc2045::cte::error,	// rewritealways
			1,
			rfc2045::cte::error	// standardize
		},
		{
			"8-bit content",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"HelloĀ\n"
			"--aaa--\n",
			1,
			rfc2045::cte::qp,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			0,
			rfc2045::cte::error,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"7-bit content",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 7bit\n"
			"\n"
			"Hello\n"
			"--aaa--\n",
			0,
			rfc2045::cte::error,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			0,
			rfc2045::cte::error,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"quoted 8bit content",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"Hello=C4=80\n"
			"--aaa--\n",
			0,
			rfc2045::cte::error,	// rewrite7
			1,
			rfc2045::cte::eightbit,	// rewrite8
			1,
			rfc2045::cte::eightbit,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"quoted 7bit content",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"Hello\n"
			"--aaa--\n",
			0,
			rfc2045::cte::error,	// rewrite7
			1,
			rfc2045::cte::sevenbit,	// rewrite8
			1,
			rfc2045::cte::sevenbit,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"long line",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 7bit\n"
			"\n"
			"[CHARS120]\n"
			"--aaa--\n",
			1,
			rfc2045::cte::qp,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			0,
			rfc2045::cte::error,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"quoted seven bit long line",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/mixed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"[CHARS60]=\n"
			"[CHARS60]\n"
			"--aaa--\n",
			1,
			rfc2045::cte::qp,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			1,
			rfc2045::cte::sevenbit,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		},
		{
			"multipart/signed",
			"Mime-Version: 1.0\n"
			"Content-Type: multipart/signed; boundary=aaa\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: 8bit\n"
			"\n"
			"content\n"
			"\n"
			"--aaa\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"Content-Transfer-Encoding: quoted-printable\n"
			"\n"
			"signature\n"
			"\n"
			"--aaa--\n",
			0,
			rfc2045::cte::error,	// rewrite7
			0,
			rfc2045::cte::error,	// rewrite8
			0,
			rfc2045::cte::error,	// rewritealways
			0,
			rfc2045::cte::error	// standardize
		}
	};

#define UPDATE_AUTOCONV 0

	const char *prefix="";
	(void)prefix;

	for (const auto &test:tests3)
	{
#if UPDATE_AUTOCONV
		auto b=test.message.begin();
		auto e=test.message.end();
		std::cout << prefix << "\t\t{\n\t\t\t\""
			  << test.test_name << "\",\n";
		bool need_final=false;

		const char *first="";
		do
		{
			std::cout << first << "\t\t\t\"";
			first="\n";

			while (b != e)
			{
				if (*b == '\n')
				{
					std::cout << "\\n\"";
					++b;
					need_final=false;
					break;
				}
				std::cout << *b;
				++b;
				need_final=true;
			}
		} while (b != e);

		if (need_final)
			std::cout << "\"";

		std::string trailing_comment;
#endif
		for (const auto &testinfo :
			     std::array<std::tuple<
			     const char *,
			     rfc2045::convert,
			     bool test3_info::*,
			     rfc2045::cte test3_info::*>, 4>{
			     {
				     {
					     "rewrite7",
					     rfc2045::convert::sevenbit,
					     &test3_info::rewrite7,
					     &test3_info::rewrite7_to
				     },
				     {
					     "rewrite8",
					     rfc2045::convert::eightbit,
					     &test3_info::rewrite8,
					     &test3_info::rewrite8_to
				     },
				     {
					     "rewritealways",
					     rfc2045::convert::eightbit_always,
					     &test3_info::rewritealways,
					     &test3_info::rewritealways_to
				     },
				     {
					     "standardize",
					     rfc2045::convert::standardize,
					     &test3_info::rewritedefault,
					     &test3_info::rewritedefault_to
				     },
			     }
		     })
		{
			const auto &[name, convert_type,
				     rewrite_ref, rewrite_to_ref] = testinfo;

			const bool &rewrite_value_ref=test.*rewrite_ref;
			const rfc2045::cte &rewrite_value_toref=
				test.*rewrite_to_ref;

			(void)name;
			(void)rewrite_value_ref;
			(void)rewrite_value_toref;

			std::string message{test.message};

			auto n=message.find("[CHARS120]");

			while (n != message.npos)
			{
				message=message.substr(0, n) + CHARS120 +
					message.substr(n+10);
				n=message.find("[CHARS120]");
			}

			n=message.find("[CHARS60]");
			while (n != message.npos)
			{
				message=message.substr(0, n) + CHARS60 +
					message.substr(n+9);
				n=message.find("[CHARS60]");
			}

			auto message_b=message.begin();
			auto message_e=message.end();
			rfc2045::entity::line_iter<false>::iter parser{
				message_b, message_e
			};

			parser.longquotedlinesize=100;
			entity={};
			entity.parse(parser);

			bool flag=entity.autoconvert_check(convert_type);
#if UPDATE_AUTOCONV
			if (entity.content_type == "multipart/signed" &&
			    !entity.subentities.empty())
			{
				std::cout
					<< "test 3 failed: "
					"multipart/signed content was not"
					" hidden\n";
				exit(1);
			}
			std::cout << "," << trailing_comment
				  << "\n\t\t\t" << (flag ? 1:0)
				  << ",\n\t\t\trfc2045::cte::";

			trailing_comment=std::string{"\t// "} + name;

			switch ((entity.subentities.empty() ? &entity:
				 &entity.subentities[0]
				)->rewrite_transfer_encoding) {
			case rfc2045::cte::error:
				std::cout << "error";
				break;
			case rfc2045::cte::sevenbit:
				std::cout << "sevenbit";
				break;
			case rfc2045::cte::eightbit:
				std::cout << "eightbit";
				break;
			case rfc2045::cte::qp:
				std::cout << "qp";
				break;
			case rfc2045::cte::base64:
				std::cout << "base64";
				break;
			}

			std::string rewritten;

			{
				std::istringstream i{message};

				std::function<void (const char *, size_t)>
					closure{
					[&]
					(const char *p, size_t n)
					{
						rewritten.insert(
							rewritten.end(),
							p, p+n
						);
					}};

				typedef rfc2045::entity::line_iter<false>
					line_iter;

				line_iter::autoconvert(
					entity,
					closure,
					*i.rdbuf()
				);

				static_assert(
					std::is_same_v<decltype(
						line_iter::autoconvert(
							entity,
							closure,
							*i.rdbuf
						)), void>,
					"Return type of autoconvert(by ref)");

				static_assert(
					std::is_same_v<decltype(
						line_iter::autoconvert(
							entity,
							std::move(closure),
							*i.rdbuf
						)), decltype(closure)>,
					"Return type of autoconvert(by val)");

			}

#else
			if (flag != rewrite_value_ref)
			{
				std::cout << "test 3 failed: "
					  << test.test_name << ": "
					  << test.message
					  << name << ": convert="
					  << flag << "\n";
				exit(1);
			}

			auto actual_to=(
				entity.subentities.empty() ? &entity:
				&entity.subentities[0]
			)->rewrite_transfer_encoding;

			if (rewrite_value_toref != actual_to)
			{
				std::cout << "test 3 failed: "
					  << test.test_name << ": "
					  << name << ": conversion="
					  << (actual_to != rfc2045::cte::error
					      ? static_cast<char>(
						      actual_to
					      ) : '?') << "\n";
				exit(1);
			}

#endif
		}
#if UPDATE_AUTOCONV
		std::cout << trailing_comment << "\n\t\t}";
		prefix=",\n";
#endif
	}

#if UPDATE_AUTOCONV
	std::cout << "\n";
#endif
}

void testautoconvert_multipart_signed()
{
	std::istringstream message{
		"Mime-Version: 1.0\n"
		"Content-Type: multipart/signed;\n"
		"    boundary=aaa\n"
		"\n"
		"\n"
		"--aaa\n"
		"Content-Type: text/plain; charset=utf-8\n"
		"\n"
		"Message content.\n"
		"\n"
		"--aaa\n"
		"Content-Type: text/plain; charset=utf-8\n"
		"\n"
		"Message signature.\n"
		"\n"
		"--aaa--\n"
	};

	rfc2045::entity e;

	{
		auto buf=message.rdbuf();

		std::istreambuf_iterator<char> message_b{buf};
		std::istreambuf_iterator<char> message_e{};
		rfc2045::entity::line_iter<false>::iter parser{message_b,
							       message_e};
		e.parse(parser);
	}
	(void)e.autoconvert_check(rfc2045::convert::standardize);

	rfc2045::entity::autoconvert_meta meta;

	std::string result;

	rfc2045::entity::line_iter<false>::autoconvert(
		e,
		[&]
		(const char *ptr, size_t s)
		{
			result += std::string_view{ptr, s};
		},
		*message.rdbuf(),
		meta);

	if (result !=
	    "Mime-Version: 1.0\n"
	    "Content-Type: multipart/signed;\n"
	    "    boundary=aaa\n"
	    "Content-Transfer-Encoding: 8bit\n"
	    "\n"
	    "\n"
	    "--aaa\n"
	    "Content-Type: text/plain; charset=utf-8\n"
	    "\n"
	    "Message content.\n"
	    "\n"
	    "--aaa\n"
	    "Content-Type: text/plain; charset=utf-8\n"
	    "\n"
	    "Message signature.\n"
	    "\n"
	    "--aaa--\n")
	{
		std::cout << "testautoconvert_multipart_signed failed.\n";
		exit(1);
	}
}

void testheaderlimit()
{
	std::istringstream i;

	i.str("12345: 89\n"
	      "12345: 89012\n"
	      "12345: 8\n"
	      "         012\n\n");

	{
		i.rdbuf()->pubseekpos(0);
		rfc2045::entity::line_iter<false>::headers h{*i.rdbuf()};

		h.max=10;

		std::string headers;

		do
		{
			auto current_header=h.current_header();

			headers += current_header;
			headers += "\n";
		} while (h.next());

		if (headers !=
		    "12345: 89\n"
		    "12345: 890\n"
		    "12345: 8 0\n"
		    "\n")
		{
			std::cout << "testheaderlimit test 1 failed:\n"
				  << headers;

			exit(1);
		}


	}

	i.str("12345: 89\r\n"
	      "12345: 890\r\n"
	      "12345: 8901\r\n"
	      "12345: 89012\r\n"
	      "12345: 8\r\n"
	      "         012\r\n"
	      "12345: 890\r\n"
	      "          2\r\n"

	      "12345: 8\r\r\n"
	      "12345: 8\r0\r\n"
	      "12345: 8\r01\r\n"
	      "12345: 8\r012\r\n"
	      "12345: 8\r\n"
	      "         012\r\n"
	      "12345: 8\r0\r\n"
	      "          2\r\n"

	);

	{
		i.rdbuf()->pubseekpos(0);
		rfc2045::entity::line_iter<true>::headers h{*i.rdbuf()};

		h.max=10;

		std::string headers;

		do
		{
			auto current_header=h.current_header();

			for (char c:current_header)
				switch (c) {
				case '\r':
					headers += "\\r";
					break;
				case '\n':
					headers += "\\n";
					break;
				default:
					headers.push_back(c);
				}
			headers += "\n";
		} while (h.next());

		if (headers !=
		    "12345: 89\n"
		    "12345: 890\n"
		    "12345: 890\n"
		    "12345: 890\n"
		    "12345: 8 0\n"
		    "12345: 890\n"
		    "12345: 8\\r\n"
		    "12345: 8\\r0\n"
		    "12345: 8\\r0\n"
		    "12345: 8\\r0\n"
		    "12345: 8 0\n"
		    "12345: 8\\r0\n")
		{
			std::cout << "testheaderlimit test 2 failed:\n"
				  << headers;
		}
	}
}

static void testbinaryencoding()
{
	std::string_view testmessage=
		"Mime-Version: 1.0\n"
		"Content-Type: multipart/mixed; boundary=aaa\n"
		"Content-Transfer-Encoding: binary\n"
		"\n"
		"--aaa\n"
		"\n"
		"\n"
		"--aaa--\n";

	auto b=testmessage.begin();
	auto e=testmessage.end();
	rfc2045::entity message;
	rfc2045::entity::line_iter<false>::iter parser{b, e};

	message.parse(parser);

	if (message.all_errors())
	{
		std::cerr << "testbinaryencoding failed.\n";
		exit(1);
	}
}

static void testappendurl()
{
	static const struct {
		const char *base;
		const char *location;
		const char *result;
	} tests[]={
		{
			"http://home.test/dir",
			"http://test.test/file",
			"http://test.test/file",
		},
		{
			"http://home.test/dir",
			"file",
			"http://home.test/dir/file",
		},
		{
			"http://home.test/dir",
			"/sub/file",
			"http://home.test/sub/file",
		},
		{
			"http://home.test/dir",
			"//test.test/sub/file",
			"http://test.test/sub/file",
		},
	};

	for (const auto &t:tests)
	{
		std::string p=rfc2045::append_url(t.base, t.location);

		if (p != t.result)
		{
			std::cerr << "rfc2045_append_url failed:\n"
				  << "expected: " << t.result << "\n"
				  << "  result: " << p << "\n";
			exit(1);
		}
	}
}

void testdecodingerror()
{
	std::stringstream testmessage{
		std::string{
			"Mime-Version: 1.0\n"
			"Content-Type: text/plain; charset=utf-8\n"
			"\n"
			"--\n"
			"A\xd0\xbf"
			"B\n"
		}
	};

	rfc2045::entity message;
	{
		std::istreambuf_iterator<char> b{testmessage.rdbuf()}, e;
		rfc2045::entity::line_iter<false>::iter parser{b, e};

		message.parse(parser);
	}

	std::ostringstream o;

	rfc2045::mime_decoder decoder{
		[&]
		(const char *ptr, size_t n)
		{
			o << std::string_view{ptr, n};
		},
		*testmessage.rdbuf(),
		"iso-8859-1"
	};

	decoder.decode_header=false;
	decoder.add_eol=true;
	decoder.decode<false>(message);

	if (!decoder.decoding_error)
	{
		std::cerr
			<< "testdecodingerror failed: decoding_error not set\n";
		exit(1);
	}
	decoder.report_decoding_error=false;

	decoder.decode<false>(message);

	if (!decoder.decoding_error)
	{
		std::cerr
			<< "testdecodingerror failed: decoding_error not set\n";
		exit(1);
	}

	decoder.report_decoding_error=true;
	decoder.decode<true>(message);

	if (!decoder.decoding_error)
	{
		std::cerr
			<< "testdecodingerror failed: decoding_error not set\n";
		exit(1);
	}
	decoder.report_decoding_error=false;

	decoder.decode<true>(message);

	if (!decoder.decoding_error)
	{
		std::cerr
			<< "testdecodingerror failed: decoding_error not set\n";
		exit(1);
	}

	auto str=o.str();

	size_t cnt=0;

	for (auto &c:str)
	{
		if (c == '<')
			cnt=2;
		else if (cnt)
		{
			--cnt;
			c='#';
		}
	}

	if (str !=
	    "--\n"
	    "A<##><##>B\n"
	    "\n"
	    "[MIME decoding error]\n"
	    "--\n"
	    "A<##><##>B\n"
	    "\n"
	    "--\n"
	    "A<##><##>B\n"
	    "\r\n"
	    "[MIME decoding error]\r\n"
	    "--\n"
	    "A<##><##>B\n"
	    "\r\n")
	{
		std::cerr << "testdecodingerror failed:\n";

		for (auto c:str)
		{
			if (c == '\r')
				std::cout << "\\r";
			else if (c == '\n')
				std::cout << "\\n\n";
			else
				std::cout << c;
		}
	}
}

static void testheadercustom()
{
	std::stringstream ss{
		"123: 456\n"
		"  13\n"
		"7: 8\n"
		"\n"
		"1: 2\n"};

	ss.rdbuf()->pubseekpos(0);
	rfc2045::entity::line_iter<false>::headers h{*ss.rdbuf(),
						    ss.str().size()};

	std::string res;

	do
	{
		const auto &[header, value]=h.name_content();

		res += header;
		res += "|";
		res += value;
		res += "\n";
	} while (h.next());

	if (res !=
	    "123|456 13\n"
	    "7|8\n"
	    "|\n"
	    "1|2\n"
	)
	{
		std::cerr << "testheadercustom failed:\n"
			  << res << "\n";
		exit(1);
	}
}

static void testboundary_detector()
{
	static const struct {
		const char *boundary;
		const char *chunk;
		bool exists;
	} tests[]={

		// Test 1
		{
			"AAA",
			"Lorem\n"
			"Upsum\n"
			"AAA\n",
			false
		},
		// Test 2
		{
			"AAA",
			"Lorem\n"
			"Upsum\n"
			"--AAA\n",
			true
		},
		// Test 3
		{
			"AAA",
			"Lorem Ipsum\n"
			"--AAAAA\n"
			"Lorem Ipsum\n",
			true
		},
		// Test 4
		{
			"1234567890",
			"Lorem\n"
			"Upsum\n"
			"--AAA\n",
			false
		},
	};

	size_t n=0;
	for (const auto &test:tests)
	{
		++n;
		std::string boundary=test.boundary;
		std::string_view chunk=test.chunk;

		rfc2045::entity::boundary_detector detector{boundary};

		while (!chunk.empty())
		{
			size_t n=chunk.size();

			if (n > 10)
				n=10;

			detector(chunk.data(), n);
			chunk=chunk.substr(n);
		}

		bool exists=detector;

		if (exists != test.exists)
		{
			std::cerr << "Boundary detector test "
				  << n << " failed.\n";
			exit(1);
		}
	}
}
int main()
{
	rfc2045::default_charset="iso-8859-1";
	testrfc2045line_iter();
	testrfc2045foldedline_iter();
	testrfc2045foldedline_iter2();
	testrfc2231headers();
	testmimeparse();
	testmimelimits();
	testtryboundary();
	testautoconvert_check();
	testautoconvert_multipart_signed();
	testheaderlimit();
	testbinaryencoding();
	testappendurl();
	testdecodingerror();
	testheadercustom();
	testboundary_detector();
	return 0;
}
