1//
2// MessageHeader.cpp
3//
4// Library: Net
5// Package: Messages
6// Module: MessageHeader
7//
8// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/Net/MessageHeader.h"
16#include "Poco/Net/NetException.h"
17#include "Poco/Net/MailRecipient.h"
18#include "Poco/String.h"
19#include "Poco/Ascii.h"
20#include "Poco/TextConverter.h"
21#include "Poco/StringTokenizer.h"
22#include "Poco/Base64Decoder.h"
23#include "Poco/UTF8Encoding.h"
24#include <sstream>
25
26
27namespace Poco {
28namespace Net {
29
30
31MessageHeader::MessageHeader():
32 _fieldLimit(DFL_FIELD_LIMIT)
33{
34}
35
36
37MessageHeader::MessageHeader(const MessageHeader& messageHeader):
38 NameValueCollection(messageHeader),
39 _fieldLimit(DFL_FIELD_LIMIT)
40{
41}
42
43
44MessageHeader::~MessageHeader()
45{
46}
47
48
49MessageHeader& MessageHeader::operator = (const MessageHeader& messageHeader)
50{
51 NameValueCollection::operator = (messageHeader);
52 return *this;
53}
54
55
56void MessageHeader::write(std::ostream& ostr) const
57{
58 NameValueCollection::ConstIterator it = begin();
59 while (it != end())
60 {
61 ostr << it->first << ": " << it->second << "\r\n";
62 ++it;
63 }
64}
65
66
67void MessageHeader::read(std::istream& istr)
68{
69 read(istr, 0);
70}
71
72
73void MessageHeader::read(std::istream& istr, RecipientList* pRecipients)
74{
75 static const int eof = std::char_traits<char>::eof();
76 std::streambuf& buf = *istr.rdbuf();
77
78 std::string name;
79 std::string value;
80 name.reserve(32);
81 value.reserve(64);
82 int ch = buf.sbumpc();
83 int fields = 0;
84 while (ch != eof && ch != '\r' && ch != '\n')
85 {
86 if (_fieldLimit > 0 && fields == _fieldLimit)
87 throw MessageException("Too many header fields");
88 name.clear();
89 value.clear();
90 while (ch != eof && ch != ':' && ch != '\n' && name.length() < MAX_NAME_LENGTH) { name += ch; ch = buf.sbumpc(); }
91 if (ch == '\n') { ch = buf.sbumpc(); continue; } // ignore invalid header lines
92 if (ch != ':') throw MessageException("Field name too long/no colon found");
93 if (ch != eof) ch = buf.sbumpc(); // ':'
94 while (ch != eof && Poco::Ascii::isSpace(ch) && ch != '\r' && ch != '\n') ch = buf.sbumpc();
95 while (ch != eof && ch != '\r' && ch != '\n' && value.length() < MAX_VALUE_LENGTH) { value += ch; ch = buf.sbumpc(); }
96 if (ch == '\r') ch = buf.sbumpc();
97 if (ch == '\n')
98 ch = buf.sbumpc();
99 else if (ch != eof)
100 throw MessageException("Field value too long/no CRLF found");
101 while (ch == ' ' || ch == '\t') // folding
102 {
103 while (ch != eof && ch != '\r' && ch != '\n' && value.length() < MAX_VALUE_LENGTH) { value += ch; ch = buf.sbumpc(); }
104 if (ch == '\r') ch = buf.sbumpc();
105 if (ch == '\n')
106 ch = buf.sbumpc();
107 else if (ch != eof)
108 throw MessageException("Folded field value too long/no CRLF found");
109 }
110 Poco::trimRightInPlace(value);
111 add(name, decodeWord(value));
112 if (pRecipients) getRecipients(name, value, pRecipients);
113 ++fields;
114
115 }
116 istr.putback(ch);
117}
118
119
120void MessageHeader::getRecipients(const std::string& name, const std::string& value, RecipientList* pRecipients)
121{
122 if(pRecipients)
123 {
124 Poco::istring iName(name.c_str());
125 MailRecipient::RecipientType type;
126 if (iName == "To") type = MailRecipient::PRIMARY_RECIPIENT;
127 else if (iName == "CC") type = MailRecipient::CC_RECIPIENT;
128 else if (iName == "BCC") type = MailRecipient::BCC_RECIPIENT;
129 else return;
130 std::string address;
131 std::string realName;
132 std::vector<std::string> elements;
133 splitElements(value, elements);
134 for(const auto& e : elements)
135 {
136 size_t pos1 = e.find('<');
137 if(pos1 != e.npos) // email in angle brackets, real name must be present
138 {
139 size_t pos2 = e.find('>');
140 if(pos2 == e.npos || pos2 <= pos1)
141 throw Poco::SyntaxException("Invalid email recipient.");
142 address = e.substr(pos1 + 1, pos2 - pos1 - 1);
143 Poco::trimInPlace(address);
144 // real name may or may not be in double quotes
145 size_t posLT = pos1; // remember angle bracket pos for case there's no double-quote
146 pos1 = e.find('"');
147 if(pos1 != e.npos)
148 {
149 pos2 = e.rfind('"', pos2);
150 if (pos2 != e.npos && pos2 > pos1)
151 realName = e.substr(pos1 + 1, pos2 - pos1 - 1);
152 else
153 throw Poco::SyntaxException("Invalid email recipient.");
154 }
155 else
156 {
157 realName = e.substr(0, posLT);
158 }
159 }
160 else
161 {
162 address = e;
163 realName.clear();
164 }
165 Poco::trimInPlace(address);
166 Poco::trimInPlace(realName);
167 pRecipients->emplace_back(MailRecipient(type, address, realName));
168 }
169 }
170}
171
172
173int MessageHeader::getFieldLimit() const
174{
175 return _fieldLimit;
176}
177
178
179void MessageHeader::setFieldLimit(int limit)
180{
181 poco_assert (limit >= 0);
182
183 _fieldLimit = limit;
184}
185
186
187bool MessageHeader::hasToken(const std::string& fieldName, const std::string& token) const
188{
189 std::string field = get(fieldName, "");
190 std::vector<std::string> tokens;
191 splitElements(field, tokens, true);
192 for (std::vector<std::string>::const_iterator it = tokens.begin(); it != tokens.end(); ++it)
193 {
194 if (Poco::icompare(*it, token) == 0)
195 return true;
196 }
197 return false;
198}
199
200
201void MessageHeader::splitElements(const std::string& s, std::vector<std::string>& elements, bool ignoreEmpty)
202{
203 elements.clear();
204 std::string::const_iterator it = s.begin();
205 std::string::const_iterator end = s.end();
206 std::string elem;
207 elem.reserve(64);
208 while (it != end)
209 {
210 if (*it == '"')
211 {
212 elem += *it++;
213 while (it != end && *it != '"')
214 {
215 if (*it == '\\')
216 {
217 ++it;
218 if (it != end) elem += *it++;
219 }
220 else elem += *it++;
221 }
222 if (it != end) elem += *it++;
223 }
224 else if (*it == '\\')
225 {
226 ++it;
227 if (it != end) elem += *it++;
228 }
229 else if (*it == ',')
230 {
231 Poco::trimInPlace(elem);
232 if (!ignoreEmpty || !elem.empty())
233 elements.push_back(elem);
234 elem.clear();
235 ++it;
236 }
237 else elem += *it++;
238 }
239 if (!elem.empty())
240 {
241 Poco::trimInPlace(elem);
242 if (!ignoreEmpty || !elem.empty())
243 elements.push_back(elem);
244 }
245}
246
247
248void MessageHeader::splitParameters(const std::string& s, std::string& value, NameValueCollection& parameters)
249{
250 value.clear();
251 parameters.clear();
252 std::string::const_iterator it = s.begin();
253 std::string::const_iterator end = s.end();
254 while (it != end && Poco::Ascii::isSpace(*it)) ++it;
255 while (it != end && *it != ';') value += *it++;
256 Poco::trimRightInPlace(value);
257 if (it != end) ++it;
258 splitParameters(it, end, parameters);
259}
260
261
262void MessageHeader::splitParameters(const std::string::const_iterator& begin, const std::string::const_iterator& end, NameValueCollection& parameters)
263{
264 std::string pname;
265 std::string pvalue;
266 pname.reserve(32);
267 pvalue.reserve(64);
268 std::string::const_iterator it = begin;
269 while (it != end)
270 {
271 pname.clear();
272 pvalue.clear();
273 while (it != end && Poco::Ascii::isSpace(*it)) ++it;
274 while (it != end && *it != '=' && *it != ';') pname += *it++;
275 Poco::trimRightInPlace(pname);
276 if (it != end && *it != ';') ++it;
277 while (it != end && Poco::Ascii::isSpace(*it)) ++it;
278 while (it != end && *it != ';')
279 {
280 if (*it == '"')
281 {
282 ++it;
283 while (it != end && *it != '"')
284 {
285 if (*it == '\\')
286 {
287 ++it;
288 if (it != end) pvalue += *it++;
289 }
290 else pvalue += *it++;
291 }
292 if (it != end) ++it;
293 }
294 else if (*it == '\\')
295 {
296 ++it;
297 if (it != end) pvalue += *it++;
298 }
299 else pvalue += *it++;
300 }
301 Poco::trimRightInPlace(pvalue);
302 if (!pname.empty()) parameters.add(pname, pvalue);
303 if (it != end) ++it;
304 }
305}
306
307
308void MessageHeader::quote(const std::string& value, std::string& result, bool allowSpace)
309{
310 bool mustQuote = false;
311 for (std::string::const_iterator it = value.begin(); !mustQuote && it != value.end(); ++it)
312 {
313 if (!Poco::Ascii::isAlphaNumeric(*it) && *it != '.' && *it != '_' && *it != '-' && !(Poco::Ascii::isSpace(*it) && allowSpace))
314 mustQuote = true;
315 }
316 if (mustQuote) result += '"';
317 result.append(value);
318 if (mustQuote) result += '"';
319}
320
321
322void MessageHeader::decodeRFC2047(const std::string& ins, std::string& outs, const std::string& charset_to)
323{
324 std::string tempout;
325 StringTokenizer tokens(ins, "?");
326
327 std::string charset = toUpper(tokens[0]);
328 std::string encoding = toUpper(tokens[1]);
329 std::string text = tokens[2];
330
331 std::istringstream istr(text);
332
333 if (encoding == "B")
334 {
335 // Base64 encoding.
336 Base64Decoder decoder(istr);
337 for (char c; decoder.get(c); tempout += c) {}
338 }
339 else if (encoding == "Q")
340 {
341 // Quoted encoding.
342 for (char c; istr.get(c);)
343 {
344 if (c == '_')
345 {
346 //RFC 2047 _ is a space.
347 tempout += " ";
348 continue;
349 }
350
351 // FIXME: check that we have enought chars-
352 if (c == '=')
353 {
354 // The next two chars are hex representation of the complete byte.
355 std::string hex;
356 for (int i = 0; i < 2; i++)
357 {
358 istr.get(c);
359 hex += c;
360 }
361 hex = toUpper(hex);
362 tempout += (char)(int)strtol(hex.c_str(), 0, 16);
363 continue;
364 }
365 tempout += c;
366 }
367 }
368 else
369 {
370 // Wrong encoding
371 outs = ins;
372 return;
373 }
374
375 // convert to the right charset.
376 if (charset != charset_to)
377 {
378 try
379 {
380 TextEncoding& enc = TextEncoding::byName(charset);
381 TextEncoding& dec = TextEncoding::byName(charset_to);
382 TextConverter converter(enc, dec);
383 converter.convert(tempout, outs);
384 }
385 catch (...)
386 {
387 // FIXME: Unsuported encoding...
388 outs = tempout;
389 }
390 }
391 else
392 {
393 // Not conversion necessary.
394 outs = tempout;
395 }
396}
397
398
399std::string MessageHeader::decodeWord(const std::string& text, const std::string& charset)
400{
401 std::string outs, tmp = text;
402 do
403 {
404 std::string tmp2;
405 // find the beginning of the next rfc2047 chunk
406 size_t pos = tmp.find("=?");
407 if (pos == std::string::npos)
408 {
409 // No more found, return
410 outs += tmp;
411 break;
412 }
413
414 // check if there is standard text before the rfc2047 chunk, and if so, copy it.
415 if (pos > 0) outs += tmp.substr(0, pos);
416
417 // remove text already copied.
418 tmp = tmp.substr(pos + 2);
419
420 // find the first separator
421 size_t pos1 = tmp.find("?");
422 if (pos1 == std::string::npos)
423 {
424 // not found.
425 outs += tmp;
426 break;
427 }
428
429 // find the second separator
430 size_t pos2 = tmp.find("?", pos1 + 1);
431 if (pos2 == std::string::npos)
432 {
433 // not found
434 outs += tmp;
435 break;
436 }
437
438 // find the end of the actual rfc2047 chunk
439 size_t pos3 = tmp.find("?=", pos2 + 1);
440 if (pos3 == std::string::npos)
441 {
442 // not found.
443 outs += tmp;
444 break;
445
446 }
447 // At this place, there are a valid rfc2047 chunk, so decode and copy the result.
448 decodeRFC2047(tmp.substr(0, pos3), tmp2, charset);
449 outs += tmp2;
450
451 // Jump at the rest of the string and repeat the whole process.
452 tmp = tmp.substr(pos3 + 2);
453 } while (true);
454
455 return outs;
456}
457
458
459} } // namespace Poco::Net
460