cpim-parser.cpp 12.3 KB
Newer Older
Ronan's avatar
Ronan committed
1 2
/*
 * cpim-parser.cpp
Ghislain MARY's avatar
Ghislain MARY committed
3
 * Copyright (C) 2010-2017 Belledonne Communications SARL
Ronan's avatar
Ronan committed
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
Ronan's avatar
Ronan committed
9 10 11 12 13 14 15
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Ronan's avatar
Ronan committed
18 19 20 21 22
 */

#include <belr/abnf.h>
#include <belr/grammarbuilder.h>

23 24
#include "linphone/utils/utils.h"

Ronan's avatar
Ronan committed
25
#include "cpim-grammar.h"
Ronan's avatar
Ronan committed
26
#include "logger/logger.h"
27
#include "object/object-p.h"
Ronan's avatar
Ronan committed
28 29 30 31 32

#include "cpim-parser.h"

// =============================================================================

33
using namespace std;
Ronan's avatar
Ronan committed
34

35
LINPHONE_BEGIN_NAMESPACE
Ronan's avatar
Ronan committed
36

37 38 39 40 41
namespace Cpim {
	class Node {
	public:
		virtual ~Node () = default;
	};
Ronan's avatar
Ronan committed
42

43 44 45
	class HeaderNode : public Node {
	public:
		HeaderNode () = default;
Ronan's avatar
Ronan committed
46

Ronan's avatar
Ronan committed
47
		explicit HeaderNode (const Header &header) : mName(header.getName()), mValue(header.getValue()) {
48 49 50 51 52 53
			// Generic header.
			const GenericHeader *genericHeader = dynamic_cast<const GenericHeader *>(&header);
			if (genericHeader) {
				for (const auto &parameter : *genericHeader->getParameters())
					mParameters += ";" + parameter.first + "=" + parameter.second;
				return;
Ronan's avatar
Ronan committed
54 55
			}

56 57 58 59 60 61
			// Subject header.
			const SubjectHeader *subjectHeader = dynamic_cast<const SubjectHeader *>(&header);
			if (subjectHeader) {
				const string language = subjectHeader->getLanguage();
				if (!language.empty())
					mParameters = ";lang=" + language;
Ronan's avatar
Ronan committed
62
			}
63
		}
Ronan's avatar
Ronan committed
64

65 66 67
		string getName () const {
			return mName;
		}
Ronan's avatar
Ronan committed
68

69 70 71
		void setName (const string &name) {
			mName = name;
		}
Ronan's avatar
Ronan committed
72

73 74 75
		string getParameters () const {
			return mParameters;
		}
Ronan's avatar
Ronan committed
76

77 78 79
		void setParameters (const string &parameters) {
			mParameters = parameters;
		}
Ronan's avatar
Ronan committed
80

81 82 83 84 85 86 87
		string getValue () const {
			return mValue;
		}

		void setValue (const string &value) {
			mValue = value;
		}
Ronan's avatar
Ronan committed
88

89
		shared_ptr<Header> createHeader (bool force) const;
Ronan's avatar
Ronan committed
90

91 92 93 94
	private:
		template<typename T>
		shared_ptr<Header> createCoreHeader (bool force) const {
			shared_ptr<T> header = make_shared<T>();
Ronan's avatar
Ronan committed
95
			if (force)
96 97 98
				header->force(mValue);
			else if (!header->setValue(mValue)) {
				lWarning() << "Unable to set value on core header: `" << mName << "` => `" << mValue << "`.";
Ronan's avatar
Ronan committed
99 100 101 102 103 104
				return nullptr;
			}

			return header;
		}

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
		string mValue;
		string mName;
		string mParameters;
	};

	template<>
	shared_ptr<Header> HeaderNode::createCoreHeader<SubjectHeader>(bool force) const {
		shared_ptr<SubjectHeader> header = make_shared<SubjectHeader>();
		const string language = mParameters.length() >= 6 ? mParameters.substr(6) : "";

		if (force)
			header->force(mValue, language);
		else if (!header->setValue(mValue) || (!language.empty() && !header->setLanguage(language))) {
			lWarning() << "Unable to set value on subject header: `" <<
				mName << "` => `" << mValue << "`, `" << language << "`.";
			return nullptr;
Ronan's avatar
Ronan committed
121 122
		}

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
		return header;
	}

	shared_ptr<Header> HeaderNode::createHeader (bool force = false) const {
		static const unordered_map<string, shared_ptr<Header>(HeaderNode::*)(bool)const> reservedHandlers = {
			{ "From", &HeaderNode::createCoreHeader<FromHeader> },
			{ "To", &HeaderNode::createCoreHeader<ToHeader> },
			{ "cc", &HeaderNode::createCoreHeader<CcHeader> },
			{ "DateTime", &HeaderNode::createCoreHeader<DateTimeHeader> },
			{ "Subject", &HeaderNode::createCoreHeader<SubjectHeader> },
			{ "NS", &HeaderNode::createCoreHeader<NsHeader> },
			{ "Require", &HeaderNode::createCoreHeader<RequireHeader> }
		};

		// Core Header.
		const auto it = reservedHandlers.find(mName);
		if (it != reservedHandlers.cend())
			return (this->*it->second)(force);

		// Generic Header
		shared_ptr<GenericHeader> genericHeader = make_shared<GenericHeader>();
		genericHeader->force(mName, mValue, mParameters);
		return genericHeader;
	}
Ronan's avatar
Ronan committed
147

148
	// -------------------------------------------------------------------------
Ronan's avatar
Ronan committed
149

150 151 152
	class ListHeaderNode :
		public Node,
		public list<shared_ptr<HeaderNode> > {};
Ronan's avatar
Ronan committed
153

154 155 156 157 158 159 160 161 162 163 164
	// -------------------------------------------------------------------------

	class MessageNode : public Node {
	public:
		void addHeaders (const shared_ptr<ListHeaderNode> &headers) {
			mHeaders->push_back(headers);
		}

		// Warning: Call this function one time!
		shared_ptr<Message> createMessage () const {
			size_t size = mHeaders->size();
165
			if (size < 2 || size > 3) {
166 167
				lWarning() << "Bad headers lists size.";
				return nullptr;
Ronan's avatar
Ronan committed
168 169
			}

170 171
			const shared_ptr<Message> message = make_shared<Message>();
			const shared_ptr<ListHeaderNode> cpimHeaders = mHeaders->front();
Ronan's avatar
Ronan committed
172

173 174 175 176 177 178 179
			if (find_if(cpimHeaders->cbegin(), cpimHeaders->cend(),
						[](const shared_ptr<const HeaderNode> &headerNode) {
							return Utils::iequals(headerNode->getName(), "content-type") && headerNode->getValue() == "Message/CPIM";
						}) == cpimHeaders->cend()) {
				lWarning() << "No MIME `Content-Type` found!";
				return nullptr;
			}
Ronan's avatar
Ronan committed
180

181 182 183 184
			// Add MIME headers.
			for (const auto &headerNode : *cpimHeaders) {
				const shared_ptr<const Header> header = headerNode->createHeader();
				if (!header || !message->addCpimHeader(*header))
Ronan's avatar
Ronan committed
185 186 187
					return nullptr;
			}

188
			// Add message headers.
189
			if (mHeaders->size() > 2) {
190
				for (const auto &headerNode : **(++mHeaders->cbegin())) {
191 192 193 194 195
					const shared_ptr<const Header> header = headerNode->createHeader();
					if (!header || !message->addMessageHeader(*header))
						return nullptr;
				}
			}
196

197
			// Add content headers.
198 199
			for (const auto &headerNode : *mHeaders->back()) {
				const shared_ptr<const Header> header = headerNode->createHeader();
200
				if (!header || !message->addContentHeader(*header))
201 202 203 204 205 206 207 208 209
					return nullptr;
			}

			return message;
		}

	private:
		shared_ptr<list<shared_ptr<ListHeaderNode> > > mHeaders = make_shared<list<shared_ptr<ListHeaderNode> > >();
	};
Ronan's avatar
Ronan committed
210 211 212 213 214 215 216 217 218 219
}

// -----------------------------------------------------------------------------

class Cpim::ParserPrivate : public ObjectPrivate {
public:
	shared_ptr<belr::Grammar> grammar;
};

Cpim::Parser::Parser () : Singleton(*new ParserPrivate) {
220
	L_D();
Ronan's avatar
Ronan committed
221 222 223 224 225

	belr::ABNFGrammarBuilder builder;

	d->grammar = builder.createFromAbnf(getGrammar(), make_shared<belr::CoreRules>());
	if (!d->grammar)
Ronan's avatar
Ronan committed
226
		lFatal() << "Unable to build CPIM grammar.";
Ronan's avatar
Ronan committed
227 228 229 230 231
}

// -----------------------------------------------------------------------------

shared_ptr<Cpim::Message> Cpim::Parser::parseMessage (const string &input) {
232
	L_D();
Ronan's avatar
Ronan committed
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261

	typedef void (list<shared_ptr<HeaderNode> >::*pushPtr)(const shared_ptr<HeaderNode> &value);

	belr::Parser<shared_ptr<Node> > parser(d->grammar);
	parser.setHandler(
		"Message", belr::make_fn(make_shared<MessageNode> )
	)->setCollector(
		"Headers", belr::make_sfn(&MessageNode::addHeaders)
	);

	parser.setHandler(
		"Headers", belr::make_fn(make_shared<ListHeaderNode> )
	)->setCollector(
		"Header", belr::make_sfn(static_cast<pushPtr>(&ListHeaderNode::push_back))
	);

	parser.setHandler(
		"Header", belr::make_fn(make_shared<HeaderNode> )
	)->setCollector(
		"Header-name", belr::make_sfn(&HeaderNode::setName)
	)->setCollector(
		"Header-value", belr::make_sfn(&HeaderNode::setValue)
	)->setCollector(
		"Header-parameters", belr::make_sfn(&HeaderNode::setParameters)
	);

	size_t parsedSize;
	shared_ptr<Node> node = parser.parseInput("Message", input, &parsedSize);
	if (!node) {
Ronan's avatar
Ronan committed
262
		lWarning() << "Unable to parse message.";
Ronan's avatar
Ronan committed
263 264 265 266 267
		return nullptr;
	}

	shared_ptr<MessageNode> messageNode = dynamic_pointer_cast<MessageNode>(node);
	if (!messageNode) {
Ronan's avatar
Ronan committed
268
		lWarning() << "Unable to cast belr result to message node.";
Ronan's avatar
Ronan committed
269 270 271 272
		return nullptr;
	}

	shared_ptr<Message> message = messageNode->createMessage();
273
	if (message) {
Ronan's avatar
Ronan committed
274
		message->setContent(input.substr(parsedSize));
275
	}
Ronan's avatar
Ronan committed
276 277 278 279 280 281 282 283 284 285 286 287 288
	return message;
}

// -----------------------------------------------------------------------------

shared_ptr<Cpim::Header> Cpim::Parser::cloneHeader (const Header &header) {
	return HeaderNode(header).createHeader(true);
}

// -----------------------------------------------------------------------------

class EmptyObject {};

Ronan's avatar
Ronan committed
289
static bool headerIsValid (const shared_ptr<belr::Grammar> &grammar, const string &input) {
Ronan's avatar
Ronan committed
290 291 292 293 294 295 296 297 298 299 300
	belr::Parser<shared_ptr<EmptyObject> > parser(grammar);
	parser.setHandler(
		"Header", belr::make_fn(make_shared<EmptyObject> )
	);

	size_t parsedSize;
	shared_ptr<EmptyObject> node = parser.parseInput("Header", input, &parsedSize);
	return node && parsedSize == input.length();
}

bool Cpim::Parser::headerNameIsValid (const string &headerName) const {
301
	L_D();
302
	return headerIsValid(d->grammar, headerName + ": value\r\n");
Ronan's avatar
Ronan committed
303 304 305
}

bool Cpim::Parser::headerValueIsValid (const string &headerValue) const {
306
	L_D();
307
	return headerIsValid(d->grammar, "key: " + headerValue + "\r\n");
Ronan's avatar
Ronan committed
308 309
}

310
bool Cpim::Parser::headerParameterIsValid (const string &headerParameter) const {
311
	L_D();
312
	return headerIsValid(d->grammar, "key:;" + headerParameter + " value\r\n");
Ronan's avatar
Ronan committed
313 314 315 316
}

// -----------------------------------------------------------------------------

Ronan's avatar
Ronan committed
317
static bool coreHeaderIsValid (
Ronan's avatar
Ronan committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
	const shared_ptr<belr::Grammar> &grammar,
	const string &headerName,
	const string &headerValue,
	const string &headerParams = string()
) {
	const string mainRule = headerName + "-header";

	belr::Parser<shared_ptr<EmptyObject> > parser(grammar);
	parser.setHandler(
		mainRule, belr::make_fn(make_shared<EmptyObject> )
	);

	const string input = headerName + ":" + headerParams + " " + headerValue;

	size_t parsedSize;
	shared_ptr<EmptyObject> node = parser.parseInput(mainRule, input, &parsedSize);
	return node && parsedSize == input.length();
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::FromHeader>(const string &headerValue) const {
339
	L_D();
340
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "From", headerValue);
Ronan's avatar
Ronan committed
341 342 343 344
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::ToHeader>(const string &headerValue) const {
345
	L_D();
346
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "To", headerValue);
Ronan's avatar
Ronan committed
347 348 349 350
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::CcHeader>(const string &headerValue) const {
351
	L_D();
352
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "cc", headerValue);
Ronan's avatar
Ronan committed
353 354 355 356 357 358
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::DateTimeHeader>(const string &headerValue) const {
	static const int daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

359
	L_D();
360
	if (!LinphonePrivate::coreHeaderIsValid(d->grammar, "DateTime", headerValue))
Ronan's avatar
Ronan committed
361 362 363
		return false;

	// Check date.
Ronan's avatar
Ronan committed
364
	const int year = Utils::stoi(headerValue.substr(0, 4));
Ronan's avatar
Ronan committed
365 366
	const bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;

Ronan's avatar
Ronan committed
367
	const int month = Utils::stoi(headerValue.substr(5, 2));
Ronan's avatar
Ronan committed
368 369 370
	if (month < 1 || month > 12)
		return false;

Ronan's avatar
Ronan committed
371
	const int day = Utils::stoi(headerValue.substr(8, 2));
Ronan's avatar
Ronan committed
372 373 374 375 376
	if (day < 1 || (month == 2 && isLeapYear ? day > 29 : day > daysInMonth[month - 1]))
		return false;

	// Check time.
	if (
Ronan's avatar
Ronan committed
377 378 379
		Utils::stoi(headerValue.substr(11, 2)) > 24 ||
		Utils::stoi(headerValue.substr(14, 2)) > 59 ||
		Utils::stoi(headerValue.substr(17, 2)) > 60
Ronan's avatar
Ronan committed
380 381 382 383 384 385 386
	)
		return false;

	// Check num offset.
	if (headerValue.back() != 'Z') {
		size_t length = headerValue.length();
		if (
Ronan's avatar
Ronan committed
387 388
			Utils::stoi(headerValue.substr(length - 5, 2)) > 24 ||
			Utils::stoi(headerValue.substr(length - 2, 2)) > 59
Ronan's avatar
Ronan committed
389 390 391 392 393 394 395 396 397
		)
			return false;
	}

	return true;
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::SubjectHeader>(const string &headerValue) const {
398
	L_D();
399
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "Subject", headerValue);
Ronan's avatar
Ronan committed
400 401 402 403
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::NsHeader>(const string &headerValue) const {
404
	L_D();
405
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "NS", headerValue);
Ronan's avatar
Ronan committed
406 407 408 409
}

template<>
bool Cpim::Parser::coreHeaderIsValid<Cpim::RequireHeader>(const string &headerValue) const {
410
	L_D();
411
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "Require", headerValue);
Ronan's avatar
Ronan committed
412 413 414 415 416
}

// -----------------------------------------------------------------------------

bool Cpim::Parser::subjectHeaderLanguageIsValid (const string &language) const {
417
	L_D();
418
	return LinphonePrivate::coreHeaderIsValid(d->grammar, "Subject", "SubjectValue", ";lang=" + language);
Ronan's avatar
Ronan committed
419
}
420 421

LINPHONE_END_NAMESPACE