/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone
 * (see https://gitlab.linphone.org/BC/public/liblinphone).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
#include <algorithm>
#include <string>

#include "content/content-disposition.h"
#include "content/content-manager.h"
#include "content/content-type.h"
#include "content/content.h"
#include "content/header/header-param.h"
#include "liblinphone_tester.h"
#include "logger/logger.h"
#include "tester_utils.h"

using namespace LinphonePrivate;
using namespace std;

static const char *type1 = "application";
static const char *type2 = "application";
static const char *type3 = "application";
static const char *type4 = "application";

static const char *subtype1 = "rlmi+xml";
static const char *subtype2 = "pidf+xml";
static const char *subtype3 = "pidf+xml";
static const char *subtype4 = "pidf+xml";

static const char *contentid1 = "sip:marie@sip.example.org;gr=urn:uuid:6cfdef8a-ae0b-4072-97bc-c0399ab9071b";
static const char *contentid2 = "sip:pauline@sip.example.org;gr=urn:uuid:2a9461cb-9014-4022-a21d-875074da7010";
static const char *contentid3 = "sip:laure@sip.example.org;gr=urn:uuid:3789446b-6278-4099-bc1a-6da95858aad2";

static const char *contentdesc1 = "Key for Marie";
static const char *contentdesc2 = "Key for Pauline";
static const char *contentdesc3 = "Key for Laure";
static const char *contentdesc4 = "Encrypted message";

static const char *source_multipart =
    "--14737809831466499882746641449\r\n"
    "Content-Type: application/rlmi+xml;charset=\"UTF-8\"\r\n"
    "Content-Id: sip:marie@sip.example.org;gr=urn:uuid:6cfdef8a-ae0b-4072-97bc-c0399ab9071b\r\n"
    "Content-Description: Key for Marie\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<list xmlns=\"urn:ietf:params:xml:ns:rlmi\" fullState=\"false\" uri=\"sip:rls@sip.linphone.org\" version=\"1\">"
    "	<resource uri=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\">"
    "	<instance cid=\"LO3VOS4@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "		</resource>"
    "	<resource uri=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\">"
    "		<instance cid=\"5v6tTNM@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "	<resource uri=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\">"
    "		<instance cid=\"P2WAj~Y@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "</list>"
    "--14737809831466499882746641449\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Id: sip:pauline@sip.example.org;gr=urn:uuid:2a9461cb-9014-4022-a21d-875074da7010\r\n"
    "Content-Description: Key for Pauline\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"qmht-9\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+YYYYYYYYYY@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "--14737809831466499882746641449\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Id: sip:laure@sip.example.org;gr=urn:uuid:3789446b-6278-4099-bc1a-6da95858aad2\r\n"
    "Content-Description: Key for Laure\r\n"
    "Content-Encoding: b64\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"szohvt\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+XXXXXXXXXX@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "--14737809831466499882746641449\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Description: Encrypted message\r\n"
    "Content-Id: toto;param1=value1;param2;param3=value3\r\n"
    "Content-Disposition: recipient-list\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"oc3e08\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:someone@sip.linphone.org</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "--14737809831466499882746641449--\r\n";

static const char *generated_multipart =
    "-----------------------------14737809831466499882746641449\r\n"
    "Content-Id: sip:marie@sip.example.org;gr=urn:uuid:6cfdef8a-ae0b-4072-97bc-c0399ab9071b\r\n"
    "Content-Description: Key for Marie\r\n"
    "Content-Type: application/rlmi+xml;charset=\"UTF-8\"\r\n"
    "Content-Length:582\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<list xmlns=\"urn:ietf:params:xml:ns:rlmi\" fullState=\"false\" uri=\"sip:rls@sip.linphone.org\" version=\"1\">"
    "	<resource uri=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\">"
    "	<instance cid=\"LO3VOS4@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "		</resource>"
    "	<resource uri=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\">"
    "		<instance cid=\"5v6tTNM@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "	<resource uri=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\">"
    "		<instance cid=\"P2WAj~Y@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "</list>"
    "-----------------------------14737809831466499882746641449\r\n"
    "Content-Id: sip:pauline@sip.example.org;gr=urn:uuid:2a9461cb-9014-4022-a21d-875074da7010\r\n"
    "Content-Description: Key for Pauline\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Length:561\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"qmht-9\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+YYYYYYYYYY@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "-----------------------------14737809831466499882746641449\r\n"
    "Content-Encoding:b64\r\n"
    "Content-Id: sip:laure@sip.example.org;gr=urn:uuid:3789446b-6278-4099-bc1a-6da95858aad2\r\n"
    "Content-Description: Key for Laure\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Length:561\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"szohvt\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+XXXXXXXXXX@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "-----------------------------14737809831466499882746641449\r\n"
    "Content-Description: Encrypted message\r\n"
    "Content-Id: toto;param1=value1;param2;param3=value3\r\n"
    "Content-Type: application/pidf+xml;charset=\"UTF-8\"\r\n"
    "Content-Length:546\r\n"
    "Content-Disposition: recipient-list\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"oc3e08\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:someone@sip.linphone.org</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>"
    "-----------------------------14737809831466499882746641449--\r\n";

static const char *part1 =
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<list xmlns=\"urn:ietf:params:xml:ns:rlmi\" fullState=\"false\" uri=\"sip:rls@sip.linphone.org\" version=\"1\">"
    "	<resource uri=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\">"
    "	<instance cid=\"LO3VOS4@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "		</resource>"
    "	<resource uri=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\">"
    "		<instance cid=\"5v6tTNM@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "	<resource uri=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\">"
    "		<instance cid=\"P2WAj~Y@sip.linphone.org\" id=\"1\" state=\"active\"/>"
    "	</resource>"
    "</list>";

static const char *part2 =
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"qmht-9\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+YYYYYYYYYY@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+YYYYYYYYYY@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>";

static const char *part3 =
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"szohvt\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:+XXXXXXXXXX@sip.linphone.org;user=phone</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+XXXXXXXXXX@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>";

static const char *part4 =
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
    "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" "
    "xmlns:p1=\"urn:ietf:params:xml:ns:pidf:data-model\">"
    "	<tuple id=\"oc3e08\">"
    "		<status>"
    "			<basic>open</basic>"
    "		</status>"
    "		<contact>sip:someone@sip.linphone.org</contact>"
    "		<timestamp>2017-10-25T13:18:26</timestamp>"
    "	</tuple>"
    "	<p1:person id=\"sip:+ZZZZZZZZZZ@sip.linphone.org;user=phone\" xmlns:p2=\"urn:ietf:params:xml:ns:pidf:rpid\">"
    "		<p2:activities>"
    "			<p2:away/>"
    "		</p2:activities>"
    "	</p1:person>"
    "</presence>";

static const char *source_multipart2 =
    "\r\n"
    "--UniqueBroadWorksBoundary\r\n"
    "\r\n"
    "Content-Type:application/rlmi+xml\r\n"
    "Content-Length:154\r\n"
    "Content-ID:<AFqAMY@broadworks>\r\n"
    "\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?><list xmlns=\"urn:ietf:params:xml:ns:rlmi\" "
    "uri=\"sip:toto@nohyphen.com\" version=\"0\" fullState=\"true\">\r\n"
    "\r\n"
    "</list>\r\n"
    "\r\n"
    "\r\n"
    "\r\n"
    "--UniqueBroadWorksBoundary--\r\n";

static const char *invalid_source_multipart =
    "\r\n"
    "--InvalidContentType\r\n"
    "\r\n"
    "Content-Type:\r\n"
    "Content-Length:154\r\n"
    "\r\n"
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?><list xmlns=\"urn:ietf:params:xml:ns:rlmi\" "
    "uri=\"sip:error@content-type.invalid\" version=\"0\" fullState=\"true\">\r\n"
    "\r\n"
    "</list>\r\n"
    "\r\n"
    "\r\n"
    "\r\n"
    "--InvalidContentType--\r\n";

static const char *part21 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><list xmlns=\"urn:ietf:params:xml:ns:rlmi\" "
                            "uri=\"sip:toto@nohyphen.com\" version=\"0\" fullState=\"true\"></list>";

void multipart_parsing() {
	Content multipartContent;
	multipartContent.setBodyFromLocale(source_multipart2);
	ContentType contentType = ContentType("multipart", "related");
	contentType.addParameter("type", "application/rlmi+xml");
	contentType.addParameter("boundary", "UniqueBroadWorksBoundary");
	multipartContent.setContentType(contentType);

	list<Content> contents = ContentManager::multipartToContentList(multipartContent);
	BC_ASSERT_EQUAL((int)contents.size(), 1, int, "%d");

	// check body
	Content content1 = contents.front();
	contents.pop_front();
	string originalStr1(part21);
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), ' '), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\t'), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\r'), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\n'), originalStr1.end());
	string generatedStr1 = content1.getBodyAsString();
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), ' '), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\t'), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\r'), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\n'), generatedStr1.end());
	ms_message("\n\n----- Generated part 1 -----");
	ms_message("%s", generatedStr1.c_str());
	ms_message("\n\n----- Original part 1 -----");
	ms_message("%s", originalStr1.c_str());
	BC_ASSERT_TRUE(originalStr1 == generatedStr1);

	// check content type
	string originalType1("application");
	string originalSubType1("rlmi+xml");
	string generatedType1 = content1.getContentType().getType();
	string generatedSubType1 = content1.getContentType().getSubType();
	ms_message("\n\nOriginal type 1 = %s", originalType1.c_str());
	ms_message("Generated type 1 = %s", generatedType1.c_str());
	ms_message("\n\nOriginal subtype 1 = %s", originalSubType1.c_str());
	ms_message("Generated subtype 1 = %s", generatedSubType1.c_str());
	BC_ASSERT_TRUE(originalType1 == generatedType1);
	BC_ASSERT_TRUE(originalSubType1 == generatedSubType1);

	// check custom headers
	string originalContentId1("<AFqAMY@broadworks>");
	string generatedContentId1 = content1.getHeader("Content-ID").getValueWithParams();

	BC_ASSERT_TRUE(originalContentId1 == generatedContentId1);
}

void invalid_multipart_parsing() {
	Content multipartContent;
	multipartContent.setBodyFromLocale(invalid_source_multipart);
	ContentType contentType = ContentType("multipart", "related");
	contentType.addParameter("type", "application/rlmi+xml");
	contentType.addParameter("boundary", "InvalidContentType");
	multipartContent.setContentType(contentType);

	list<Content> contents = ContentManager::multipartToContentList(multipartContent);
	BC_ASSERT_EQUAL((int)contents.size(), 1, int, "%d");
}

void multipart_to_list() {
	Content multipartContent;
	multipartContent.setBodyFromLocale(source_multipart);
	multipartContent.setContentType(ContentType("multipart", "related"));

	list<Content> contents = ContentManager::multipartToContentList(multipartContent);
	BC_ASSERT_EQUAL((int)contents.size(), 4, int, "%d");

	// check body
	Content content1 = contents.front();
	contents.pop_front();
	string originalStr1(part1);
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), ' '), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\t'), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\r'), originalStr1.end());
	originalStr1.erase(std::remove(originalStr1.begin(), originalStr1.end(), '\n'), originalStr1.end());
	string generatedStr1 = content1.getBodyAsString();
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), ' '), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\t'), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\r'), generatedStr1.end());
	generatedStr1.erase(std::remove(generatedStr1.begin(), generatedStr1.end(), '\n'), generatedStr1.end());
	ms_message("\n\n----- Generated part 1 -----");
	ms_message("%s", generatedStr1.c_str());
	ms_message("\n\n----- Original part 1 -----");
	ms_message("%s", originalStr1.c_str());
	BC_ASSERT_TRUE(originalStr1 == generatedStr1);

	// check content type
	string originalType1(type1);
	string originalSubType1(subtype1);
	string generatedType1 = content1.getContentType().getType();
	string generatedSubType1 = content1.getContentType().getSubType();
	ms_message("\n\nOriginal type 1 = %s", originalType1.c_str());
	ms_message("Generated type 1 = %s", generatedType1.c_str());
	ms_message("\n\nOriginal subtype 1 = %s", originalSubType1.c_str());
	ms_message("Generated subtype 1 = %s", generatedSubType1.c_str());
	BC_ASSERT_TRUE(originalType1 == generatedType1);
	BC_ASSERT_TRUE(originalSubType1 == generatedSubType1);

	// check custom headers
	string originalContentId1(contentid1);
	string originalContentDesc1(contentdesc1);
	string generatedContentId1 = content1.getHeader("Content-Id").getValueWithParams();
	string generatedContentDesc1 = content1.getHeader("Content-Description").getValue();

	BC_ASSERT_TRUE(originalContentId1 == generatedContentId1);
	BC_ASSERT_TRUE(originalContentDesc1 == generatedContentDesc1);

	Content content2 = contents.front();
	contents.pop_front();
	string originalStr2(part2);
	originalStr2.erase(std::remove(originalStr2.begin(), originalStr2.end(), ' '), originalStr2.end());
	originalStr2.erase(std::remove(originalStr2.begin(), originalStr2.end(), '\t'), originalStr2.end());
	originalStr2.erase(std::remove(originalStr2.begin(), originalStr2.end(), '\r'), originalStr2.end());
	originalStr2.erase(std::remove(originalStr2.begin(), originalStr2.end(), '\n'), originalStr2.end());
	string generatedStr2 = content2.getBodyAsString();
	generatedStr2.erase(std::remove(generatedStr2.begin(), generatedStr2.end(), ' '), generatedStr2.end());
	generatedStr2.erase(std::remove(generatedStr2.begin(), generatedStr2.end(), '\t'), generatedStr2.end());
	generatedStr2.erase(std::remove(generatedStr2.begin(), generatedStr2.end(), '\r'), generatedStr2.end());
	generatedStr2.erase(std::remove(generatedStr2.begin(), generatedStr2.end(), '\n'), generatedStr2.end());
	ms_message("\n\n----- Generated part 2 -----");
	ms_message("%s", generatedStr2.c_str());
	ms_message("\n\n----- Original part 2 -----");
	ms_message("%s", originalStr2.c_str());
	BC_ASSERT_TRUE(originalStr2 == generatedStr2);

	string originalType2(type2);
	string originalSubType2(subtype2);
	string generatedType2 = content2.getContentType().getType();
	string generatedSubType2 = content2.getContentType().getSubType();
	ms_message("\n\nOriginal type 2 = %s", originalType2.c_str());
	ms_message("Generated type 2 = %s", generatedType2.c_str());
	ms_message("\n\nOriginal subtype 2 = %s", originalSubType2.c_str());
	ms_message("Generated subtype 2 = %s", generatedSubType2.c_str());
	BC_ASSERT_TRUE(originalType2 == generatedType2);
	BC_ASSERT_TRUE(originalSubType2 == generatedSubType2);

	string originalContentId2(contentid2);
	string originalContentDesc2(contentdesc2);
	string generatedContentId2 = content2.getHeader("Content-Id").getValueWithParams();
	string generatedContentDesc2 = content2.getHeader("Content-Description").getValue();
	BC_ASSERT_TRUE(originalContentId2 == generatedContentId2);
	BC_ASSERT_TRUE(originalContentDesc2 == generatedContentDesc2);

	Content content3 = contents.front();
	contents.pop_front();
	string originalStr3(part3);
	originalStr3.erase(std::remove(originalStr3.begin(), originalStr3.end(), ' '), originalStr3.end());
	originalStr3.erase(std::remove(originalStr3.begin(), originalStr3.end(), '\t'), originalStr3.end());
	originalStr3.erase(std::remove(originalStr3.begin(), originalStr3.end(), '\r'), originalStr3.end());
	originalStr3.erase(std::remove(originalStr3.begin(), originalStr3.end(), '\n'), originalStr3.end());
	string generatedStr3 = content3.getBodyAsString();
	generatedStr3.erase(std::remove(generatedStr3.begin(), generatedStr3.end(), ' '), generatedStr3.end());
	generatedStr3.erase(std::remove(generatedStr3.begin(), generatedStr3.end(), '\t'), generatedStr3.end());
	generatedStr3.erase(std::remove(generatedStr3.begin(), generatedStr3.end(), '\r'), generatedStr3.end());
	generatedStr3.erase(std::remove(generatedStr3.begin(), generatedStr3.end(), '\n'), generatedStr3.end());
	ms_message("\n\n----- Generated part 3 -----");
	ms_message("%s", generatedStr3.c_str());
	ms_message("\n\n----- Original part 3 -----");
	ms_message("%s", originalStr3.c_str());
	BC_ASSERT_TRUE(originalStr3 == generatedStr3);
	BC_ASSERT_TRUE(content3.getHeader("Content-Encoding").getValue() == "b64");

	string originalType3(type3);
	string originalSubType3(subtype3);
	string generatedType3 = content3.getContentType().getType();
	string generatedSubType3 = content3.getContentType().getSubType();
	ms_message("\n\nOriginal type 3 = %s", originalType3.c_str());
	ms_message("Generated type 3 = %s", generatedType3.c_str());
	ms_message("\n\nOriginal subtype 3 = %s", originalSubType3.c_str());
	ms_message("Generated subtype 3 = %s", generatedSubType3.c_str());
	BC_ASSERT_TRUE(originalType3 == generatedType3);
	BC_ASSERT_TRUE(originalSubType3 == generatedSubType3);

	string originalContentId3(contentid3);
	string originalContentDesc3(contentdesc3);
	string generatedContentId3 = content3.getHeader("Content-Id").getValueWithParams();
	string generatedContentDesc3 = content3.getHeader("Content-Description").getValue();
	BC_ASSERT_TRUE(originalContentId3 == generatedContentId3);
	BC_ASSERT_TRUE(originalContentDesc3 == generatedContentDesc3);

	Content content4 = contents.front();
	contents.pop_front();
	string originalStr4(part4);
	originalStr4.erase(std::remove(originalStr4.begin(), originalStr4.end(), ' '), originalStr4.end());
	originalStr4.erase(std::remove(originalStr4.begin(), originalStr4.end(), '\t'), originalStr4.end());
	originalStr4.erase(std::remove(originalStr4.begin(), originalStr4.end(), '\r'), originalStr4.end());
	originalStr4.erase(std::remove(originalStr4.begin(), originalStr4.end(), '\n'), originalStr4.end());
	string generatedStr4 = content4.getBodyAsString();
	generatedStr4.erase(std::remove(generatedStr4.begin(), generatedStr4.end(), ' '), generatedStr4.end());
	generatedStr4.erase(std::remove(generatedStr4.begin(), generatedStr4.end(), '\t'), generatedStr4.end());
	generatedStr4.erase(std::remove(generatedStr4.begin(), generatedStr4.end(), '\r'), generatedStr4.end());
	generatedStr4.erase(std::remove(generatedStr4.begin(), generatedStr4.end(), '\n'), generatedStr4.end());
	ms_message("\n\n----- Generated part 4 -----");
	ms_message("%s", generatedStr4.c_str());
	ms_message("\n\n----- Original part 4 -----");
	ms_message("%s", originalStr4.c_str());
	BC_ASSERT_TRUE(originalStr4 == generatedStr4);

	string originalType4(type4);
	string originalSubType4(subtype4);
	string generatedType4 = content4.getContentType().getType();
	string generatedSubType4 = content4.getContentType().getSubType();
	ms_message("\n\nOriginal type 4 = %s", originalType4.c_str());
	ms_message("Generated type 4 = %s", generatedType4.c_str());
	ms_message("\n\nOriginal subtype 4 = %s", originalSubType4.c_str());
	ms_message("Generated subtype 4 = %s", generatedSubType4.c_str());
	BC_ASSERT_TRUE(originalType4 == generatedType4);
	BC_ASSERT_TRUE(originalSubType4 == generatedSubType4);

	string originalContentDesc4(contentdesc4);
	string generatedContentDesc4 = content4.getHeader("Content-Description").getValue();
	BC_ASSERT_TRUE(originalContentDesc4 == generatedContentDesc4);

	BC_ASSERT_TRUE(content4.getHeader("Content-Id").getValue() == "toto");
	BC_ASSERT_TRUE(content4.getHeader("Content-Id").getParameter("param1").getValue() == "value1");
	BC_ASSERT_TRUE(content4.getHeader("Content-Id").getParameter("param2").getValue().empty());
	BC_ASSERT_TRUE(content4.getHeader("Content-Id").getParameter("param3").getValue() == "value3");
	BC_ASSERT_TRUE(content4.getContentDisposition() == ContentDisposition::RecipientList);
}

void list_to_multipart() {
	ContentType contentType = ContentType("application", "rlmi+xml");
	contentType.addParameter("charset", "\"UTF-8\"");
	Content content1;
	content1.setBodyFromLocale(part1);
	content1.setContentType(contentType);
	Header contentId1("Content-Id", "sip:marie@sip.example.org;gr=urn:uuid:6cfdef8a-ae0b-4072-97bc-c0399ab9071b");
	Header contentDesc1("Content-Description", "Key for Marie");
	content1.addHeader(contentId1);
	content1.addHeader(contentDesc1);

	contentType = ContentType("application", "pidf+xml");
	contentType.addParameter("charset", "\"UTF-8\"");
	Content content2;
	content2.setBodyFromLocale(part2);
	content2.setContentType(contentType);
	Header contentId2("Content-Id", "sip:pauline@sip.example.org;gr=urn:uuid:2a9461cb-9014-4022-a21d-875074da7010");
	Header contentDesc2("Content-Description", "Key for Pauline");
	content2.addHeader(contentId2);
	content2.addHeader(contentDesc2);

	Content content3;
	content3.setBodyFromLocale(part3);
	content3.setContentType(contentType);
	Header contentEncoding3("Content-Encoding", "b64");
	Header contentId3("Content-Id", "sip:laure@sip.example.org;gr=urn:uuid:3789446b-6278-4099-bc1a-6da95858aad2");
	Header contentDesc3("Content-Description", "Key for Laure");
	content3.addHeader(contentEncoding3);
	content3.addHeader(contentId3);
	content3.addHeader(contentDesc3);

	Content content4;
	content4.setBodyFromLocale(part4);
	content4.setContentType(ContentType("application", "pidf+xml"));
	Header contentDesc4("Content-Description", "Encrypted message");
	content4.addHeader(contentDesc4);
	content4.setContentDisposition(ContentDisposition::RecipientList);
	Header header = Header("Content-Id", "toto");
	header.addParameter("param1", "value1");
	header.addParameter("param2", "");
	header.addParameter("param3", "value3");
	content4.addHeader(header);

	content4.setContentType(contentType);
	list<Content *> contents = {&content1, &content2, &content3, &content4};

	Content multipartContent = ContentManager::contentListToMultipart(
	    contents, "---------------------------14737809831466499882746641449", false);

	string originalStr(generated_multipart);
	originalStr.erase(std::remove(originalStr.begin(), originalStr.end(), ' '), originalStr.end());
	originalStr.erase(std::remove(originalStr.begin(), originalStr.end(), '\t'), originalStr.end());
	originalStr.erase(std::remove(originalStr.begin(), originalStr.end(), '\r'), originalStr.end());
	originalStr.erase(std::remove(originalStr.begin(), originalStr.end(), '\n'), originalStr.end());

	string generatedStr = multipartContent.getBodyAsString();
	generatedStr.erase(std::remove(generatedStr.begin(), generatedStr.end(), ' '), generatedStr.end());
	generatedStr.erase(std::remove(generatedStr.begin(), generatedStr.end(), '\t'), generatedStr.end());
	generatedStr.erase(std::remove(generatedStr.begin(), generatedStr.end(), '\r'), generatedStr.end());
	generatedStr.erase(std::remove(generatedStr.begin(), generatedStr.end(), '\n'), generatedStr.end());

	ms_message("\n\n----- Generated multipart -----");
	ms_message("%s", generatedStr.c_str());

	ms_message("\n\n----- Original multipart -----");
	ms_message("%s", originalStr.c_str());
	BC_ASSERT_TRUE(originalStr == generatedStr);
}

static void content_type_parsing(void) {
	string type = "message/external-body;access-type=URL;URL=\"https://www.linphone.org/img/"
	              "linphone-open-source-voip-projectX2.png\"";
	ContentType contentType = ContentType(type);
	BC_ASSERT_STRING_EQUAL("message", contentType.getType().c_str());
	BC_ASSERT_STRING_EQUAL("external-body", contentType.getSubType().c_str());
	BC_ASSERT_STRING_EQUAL("URL", contentType.getParameter("access-type").getValue().c_str());
	BC_ASSERT_STRING_EQUAL("\"https://www.linphone.org/img/linphone-open-source-voip-projectX2.png\"",
	                       contentType.getParameter("URL").getValue().c_str());
	BC_ASSERT_STRING_EQUAL("", contentType.getParameter("boundary").getValue().c_str());
	BC_ASSERT_EQUAL(2, (int)contentType.getParameters().size(), int, "%d");
	lInfo() << "Content-Type is " << contentType;
	BC_ASSERT_STRING_EQUAL(contentType.getName().c_str(), "Content-Type");
	BC_ASSERT_TRUE(type == contentType.getValueWithParams());

	type = "multipart/mixed;boundary=-----------------------------14737809831466499882746641450";
	contentType = ContentType(type);
	BC_ASSERT_STRING_EQUAL("multipart", contentType.getType().c_str());
	BC_ASSERT_STRING_EQUAL("mixed", contentType.getSubType().c_str());
	BC_ASSERT_STRING_EQUAL("-----------------------------14737809831466499882746641450",
	                       contentType.getParameter("boundary").getValue().c_str());
	BC_ASSERT_STRING_EQUAL("", contentType.getParameter("access-type").getValue().c_str());
	BC_ASSERT_EQUAL(1, (int)contentType.getParameters().size(), int, "%d");
	lInfo() << "Content-Type is " << contentType;
	BC_ASSERT_STRING_EQUAL(contentType.getName().c_str(), "Content-Type");
	BC_ASSERT_TRUE(type == contentType.getValueWithParams());

	type = "multipart/related;type=\"application/rlmi+xml\";boundary=UniqueBroadWorksBoundary";
	contentType = ContentType(type);
	BC_ASSERT_STRING_EQUAL("multipart", contentType.getType().c_str());
	BC_ASSERT_STRING_EQUAL("related", contentType.getSubType().c_str());
	BC_ASSERT_STRING_EQUAL("UniqueBroadWorksBoundary", contentType.getParameter("boundary").getValue().c_str());
	BC_ASSERT_STRING_EQUAL("\"application/rlmi+xml\"", contentType.getParameter("type").getValue().c_str());
	BC_ASSERT_EQUAL(2, (int)contentType.getParameters().size(), int, "%d");
	lInfo() << "Content-Type is " << contentType;
	BC_ASSERT_STRING_EQUAL(contentType.getName().c_str(), "Content-Type");
	BC_ASSERT_TRUE(type == contentType.getValueWithParams());

	type = "plain/text";
	contentType = ContentType(type);
	BC_ASSERT_STRING_EQUAL("plain", contentType.getType().c_str());
	BC_ASSERT_STRING_EQUAL("text", contentType.getSubType().c_str());
	BC_ASSERT_STRING_EQUAL("", contentType.getParameter("boundary").getValue().c_str());
	BC_ASSERT_EQUAL(0, (int)contentType.getParameters().size(), int, "%d");
	lInfo() << "Content-Type is " << contentType;
	BC_ASSERT_STRING_EQUAL(contentType.getName().c_str(), "Content-Type");
	BC_ASSERT_TRUE(type == contentType.getValueWithParams());
}

static void content_header_parsing(void) {
	string value = "toto;param1=value1;param2;param3=value3";
	Header header = Header("Content-Id", value);
	BC_ASSERT_TRUE(header.getValue() == "toto");
	BC_ASSERT_TRUE(header.getParameter("param1").getValue() == "value1");
	BC_ASSERT_TRUE(header.getParameter("param2").getValue().empty());
	BC_ASSERT_TRUE(header.getParameter("param3").getValue() == "value3");
	BC_ASSERT_EQUAL(3, (int)header.getParameters().size(), int, "%d");
	BC_ASSERT_STRING_EQUAL("", header.getParameter("encoding").getValue().c_str());
	BC_ASSERT_STRING_EQUAL(header.getName().c_str(), "Content-Id");
	BC_ASSERT_TRUE(header.getValueWithParams() == value);

	value = "b64";
	header = Header("Content-Encoding", value);
	BC_ASSERT_TRUE(header.getValue() == value);
	BC_ASSERT_EQUAL(0, (int)header.getParameters().size(), int, "%d");
	BC_ASSERT_STRING_EQUAL("", header.getParameter("access-type").getValue().c_str());
	BC_ASSERT_STRING_EQUAL(header.getName().c_str(), "Content-Encoding");
	BC_ASSERT_TRUE(header.getValueWithParams() == value);
}

static void content_public_api(void) {
	LinphoneContent *content = linphone_factory_create_content(linphone_factory_get());

	const char *type = "application";
	const char *subtype = "resource-lists+xml";
	const char *disposition = "recipient-list";
	const char *encoding = "b64";
	const char *custom_header_name = "X-LINPHONE-TEST";
	const char *custom_header_value = "1";

	linphone_content_set_type(content, type);
	linphone_content_set_subtype(content, subtype);
	linphone_content_set_disposition(content, disposition);
	linphone_content_set_encoding(content, encoding);
	linphone_content_add_custom_header(content, custom_header_name, custom_header_value);

	BC_ASSERT_STRING_EQUAL(linphone_content_get_type(content), type);
	BC_ASSERT_STRING_EQUAL(linphone_content_get_subtype(content), subtype);
	BC_ASSERT_STRING_EQUAL(linphone_content_get_disposition(content), disposition);
	BC_ASSERT_STRING_EQUAL(linphone_content_get_encoding(content), encoding);
	BC_ASSERT_STRING_EQUAL(linphone_content_get_custom_header(content, custom_header_name), custom_header_value);

	linphone_content_unref(content);
}

test_t contents_tests[] = {TEST_NO_TAG("Multipart to list", multipart_to_list),
                           TEST_NO_TAG("Multipart parsing", multipart_parsing),
                           TEST_NO_TAG("Invalid multipart parsing", invalid_multipart_parsing),
                           TEST_NO_TAG("List to multipart", list_to_multipart),
                           TEST_NO_TAG("Content type parsing", content_type_parsing),
                           TEST_NO_TAG("Content header parsing", content_header_parsing),
                           TEST_NO_TAG("Content C public API", content_public_api)};

test_suite_t contents_test_suite = {"Contents",
                                    nullptr,
                                    nullptr,
                                    liblinphone_tester_before_each,
                                    liblinphone_tester_after_each,
                                    sizeof(contents_tests) / sizeof(contents_tests[0]),
                                    contents_tests,
                                    0};
