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 | |
27 | namespace Poco { |
28 | namespace Net { |
29 | |
30 | |
31 | MessageHeader::(): |
32 | _fieldLimit(DFL_FIELD_LIMIT) |
33 | { |
34 | } |
35 | |
36 | |
37 | MessageHeader::(const MessageHeader& ): |
38 | NameValueCollection(messageHeader), |
39 | _fieldLimit(DFL_FIELD_LIMIT) |
40 | { |
41 | } |
42 | |
43 | |
44 | MessageHeader::() |
45 | { |
46 | } |
47 | |
48 | |
49 | MessageHeader& MessageHeader:: (const MessageHeader& ) |
50 | { |
51 | NameValueCollection::operator = (messageHeader); |
52 | return *this; |
53 | } |
54 | |
55 | |
56 | void MessageHeader::(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 | |
67 | void MessageHeader::(std::istream& istr) |
68 | { |
69 | read(istr, 0); |
70 | } |
71 | |
72 | |
73 | void MessageHeader::(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 += static_cast<char>(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 += static_cast<char>(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 += static_cast<char>(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(static_cast<char>(ch)); |
117 | } |
118 | |
119 | |
120 | void MessageHeader::(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 | |
173 | int MessageHeader::() const |
174 | { |
175 | return _fieldLimit; |
176 | } |
177 | |
178 | |
179 | void MessageHeader::(int limit) |
180 | { |
181 | poco_assert (limit >= 0); |
182 | |
183 | _fieldLimit = limit; |
184 | } |
185 | |
186 | |
187 | bool MessageHeader::(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 | |
201 | void MessageHeader::(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 | |
248 | void MessageHeader::(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 | |
262 | void MessageHeader::(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 | |
308 | void MessageHeader::(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 | |
322 | void MessageHeader::(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 | |
399 | std::string MessageHeader::(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 | |