1//
2// MailMessage.cpp
3//
4// Library: Net
5// Package: Mail
6// Module: MailMessage
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/MailMessage.h"
16#include "Poco/Net/MediaType.h"
17#include "Poco/Net/MultipartReader.h"
18#include "Poco/Net/MultipartWriter.h"
19#include "Poco/Net/PartSource.h"
20#include "Poco/Net/PartHandler.h"
21#include "Poco/Net/StringPartSource.h"
22#include "Poco/Net/QuotedPrintableEncoder.h"
23#include "Poco/Net/QuotedPrintableDecoder.h"
24#include "Poco/Net/NameValueCollection.h"
25#include "Poco/Base64Encoder.h"
26#include "Poco/Base64Decoder.h"
27#include "Poco/StreamCopier.h"
28#include "Poco/DateTimeFormat.h"
29#include "Poco/DateTimeFormatter.h"
30#include "Poco/DateTimeParser.h"
31#include "Poco/String.h"
32#include "Poco/Format.h"
33#include "Poco/StringTokenizer.h"
34#include "Poco/StreamCopier.h"
35#include "Poco/NumberFormatter.h"
36#include "Poco/TextEncoding.h"
37#include "Poco/TextConverter.h"
38#include "Poco/NumberParser.h"
39#include "Poco/Ascii.h"
40#include <sstream>
41
42
43using Poco::Base64Encoder;
44using Poco::Base64Decoder;
45using Poco::StreamCopier;
46using Poco::DateTimeFormat;
47using Poco::DateTimeFormatter;
48using Poco::DateTimeParser;
49using Poco::StringTokenizer;
50using Poco::TextEncoding;
51using Poco::TextEncodingRegistry;
52using Poco::TextConverter;
53using Poco::NumberParser;
54using Poco::Ascii;
55using Poco::icompare;
56
57
58namespace Poco {
59namespace Net {
60
61
62namespace
63{
64 class MultiPartHandler: public PartHandler
65 /// This is a default part handler for multipart messages, used when there
66 /// is no external handler provided to he MailMessage. This handler
67 /// will handle all types of message parts, including attachments.
68 {
69 public:
70 MultiPartHandler(MailMessage* pMsg): _pMsg(pMsg)
71 /// Creates multi part handler.
72 /// The pMsg pointer points to the calling MailMessage
73 /// and will be used to properly populate it, so the
74 /// message content could be written out unmodified
75 /// in its entirety, including attachments.
76 {
77 }
78
79 ~MultiPartHandler()
80 /// Destroys string part handler.
81 {
82 }
83
84 void handlePart(const MessageHeader& header, std::istream& stream)
85 /// Handles a part. If message pointer was provided at construction time,
86 /// the message pointed to will be properly populated so it could be written
87 /// back out at a later point in time.
88 {
89 std::string tmp;
90 Poco::StreamCopier::copyToString(stream, tmp);
91 if (_pMsg)
92 {
93 MailMessage::ContentTransferEncoding cte = MailMessage::ENCODING_7BIT;
94 if (header.has(MailMessage::HEADER_CONTENT_TRANSFER_ENCODING))
95 {
96 std::string enc = header[MailMessage::HEADER_CONTENT_TRANSFER_ENCODING];
97 if (enc == MailMessage::CTE_8BIT)
98 cte = MailMessage::ENCODING_8BIT;
99 else if (enc == MailMessage::CTE_QUOTED_PRINTABLE)
100 cte = MailMessage::ENCODING_QUOTED_PRINTABLE;
101 else if (enc == MailMessage::CTE_BASE64)
102 cte = MailMessage::ENCODING_BASE64;
103 }
104
105 std::string contentType = header.get(MailMessage::HEADER_CONTENT_TYPE, "text/plain; charset=us-ascii");
106 std::string contentDisp = header.get(MailMessage::HEADER_CONTENT_DISPOSITION, "");
107 std::string filename;
108 if (!contentDisp.empty())
109 filename = getParamFromHeader(contentDisp, "filename");
110 if (filename.empty())
111 filename = getParamFromHeader(contentType, "name");
112 Poco::SharedPtr<PartSource> pPS = _pMsg->createPartStore(tmp, contentType, filename);
113 poco_check_ptr (pPS);
114 NameValueCollection::ConstIterator it = header.begin();
115 NameValueCollection::ConstIterator end = header.end();
116 bool added = false;
117 for (; it != end; ++it)
118 {
119 if (!added && MailMessage::HEADER_CONTENT_DISPOSITION == it->first)
120 {
121 if (it->second == "inline")
122 _pMsg->addContent(pPS, cte);
123 else
124 _pMsg->addAttachment(getPartName(header), pPS, cte);
125 added = true;
126 }
127
128 pPS->headers().set(it->first, it->second);
129 }
130
131 if (contentDisp.empty())
132 {
133 _pMsg->addContent(pPS, cte);
134 }
135 }
136 }
137
138 private:
139 std::string getParamFromHeader(const std::string& header, const std::string& param)
140 {
141 StringTokenizer st(header, ";=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
142 StringTokenizer::Iterator it = st.begin();
143 StringTokenizer::Iterator end = st.end();
144 for (; it != end; ++it) { if (icompare(*it, param) == 0) break; }
145 if (it != end)
146 {
147 ++it;
148 if (it == end) return "";
149 return *it;
150 }
151 return "";
152 }
153
154 std::string getPartName(const MessageHeader& header)
155 {
156 std::string ct = MailMessage::HEADER_CONTENT_TYPE;
157 if (header.has(ct))
158 return getParamFromHeader(header[ct], "name");
159 return "";
160 }
161
162 MailMessage* _pMsg;
163 };
164
165
166 class StringPartHandler: public PartHandler
167 /// This is a default part handler, used when there is no
168 /// external handler provided to the MailMessage. This handler
169 /// handles only single-part messages.
170 {
171 public:
172 StringPartHandler(std::string& content): _str(content)
173 /// Creates string part handler.
174 /// The content parameter represents the part content.
175 {
176 }
177
178 ~StringPartHandler()
179 /// Destroys string part handler.
180 {
181 }
182
183 void handlePart(const MessageHeader& /*header*/, std::istream& stream)
184 /// Handles a part.
185 {
186 std::string tmp;
187 Poco::StreamCopier::copyToString(stream, tmp);
188 _str.append(tmp);
189 }
190
191 private:
192 std::string& _str;
193 };
194}
195
196
197const std::string MailMessage::HEADER_SUBJECT("Subject");
198const std::string MailMessage::HEADER_FROM("From");
199const std::string MailMessage::HEADER_TO("To");
200const std::string MailMessage::HEADER_CC("CC");
201const std::string MailMessage::HEADER_BCC("BCC");
202const std::string MailMessage::HEADER_DATE("Date");
203const std::string MailMessage::HEADER_CONTENT_TYPE("Content-Type");
204const std::string MailMessage::HEADER_CONTENT_TRANSFER_ENCODING("Content-Transfer-Encoding");
205const std::string MailMessage::HEADER_CONTENT_DISPOSITION("Content-Disposition");
206const std::string MailMessage::HEADER_CONTENT_ID("Content-ID");
207const std::string MailMessage::HEADER_MIME_VERSION("Mime-Version");
208const std::string MailMessage::EMPTY_HEADER;
209const std::string MailMessage::TEXT_PLAIN("text/plain");
210const std::string MailMessage::CTE_7BIT("7bit");
211const std::string MailMessage::CTE_8BIT("8bit");
212const std::string MailMessage::CTE_QUOTED_PRINTABLE("quoted-printable");
213const std::string MailMessage::CTE_BASE64("base64");
214
215
216MailMessage::MailMessage(PartStoreFactory* pStoreFactory):
217 _encoding(),
218 _pStoreFactory(pStoreFactory)
219{
220 Poco::Timestamp now;
221 setDate(now);
222 setContentType("text/plain");
223}
224
225
226MailMessage::MailMessage(MailMessage&& other):
227 _recipients(std::move(other._recipients)),
228 _content(std::move(other._content)),
229 _encoding(other._encoding),
230 _boundary(std::move(other._boundary)),
231 _pStoreFactory(other._pStoreFactory)
232{
233 other._recipients.clear();
234 other._content.clear();
235 other._boundary.clear();
236 other._pStoreFactory = 0;
237}
238
239
240MailMessage& MailMessage::operator = (MailMessage&& other)
241{
242 if (&other != this)
243 {
244 _recipients = std::move(other._recipients);
245 other._recipients.clear();
246 _content = std::move(other._content);
247 other._content.clear();
248 _encoding = other._encoding;
249 _boundary = std::move(other._boundary);
250 other._boundary.clear();
251 _pStoreFactory = other._pStoreFactory;
252 other._pStoreFactory = 0;
253 }
254 return *this;
255}
256
257
258MailMessage::~MailMessage()
259{
260}
261
262
263void MailMessage::addRecipient(const MailRecipient& recipient)
264{
265 _recipients.push_back(recipient);
266}
267
268
269void MailMessage::setRecipients(const Recipients& recipients)
270{
271 _recipients.assign(recipients.begin(), recipients.end());
272}
273
274
275void MailMessage::setSender(const std::string& sender)
276{
277 set(HEADER_FROM, sender);
278}
279
280
281const std::string& MailMessage::getSender() const
282{
283 if (has(HEADER_FROM))
284 return get(HEADER_FROM);
285 else
286 return EMPTY_HEADER;
287}
288
289
290void MailMessage::setSubject(const std::string& subject)
291{
292 set(HEADER_SUBJECT, subject);
293}
294
295
296const std::string& MailMessage::getSubject() const
297{
298 if (has(HEADER_SUBJECT))
299 return get(HEADER_SUBJECT);
300 else
301 return EMPTY_HEADER;
302}
303
304
305void MailMessage::setContent(const std::string& content, ContentTransferEncoding encoding)
306{
307 _content = content;
308 _encoding = encoding;
309 set(HEADER_CONTENT_TRANSFER_ENCODING, contentTransferEncodingToString(encoding));
310}
311
312
313void MailMessage::setContentType(const std::string& mediaType)
314{
315 set(HEADER_CONTENT_TYPE, mediaType);
316}
317
318
319void MailMessage::setContentType(const MediaType& mediaType)
320{
321 setContentType(mediaType.toString());
322}
323
324
325const std::string& MailMessage::getContentType() const
326{
327 if (has(HEADER_CONTENT_TYPE))
328 return get(HEADER_CONTENT_TYPE);
329 else
330 return TEXT_PLAIN;
331}
332
333
334void MailMessage::setDate(const Poco::Timestamp& dateTime)
335{
336 set(HEADER_DATE, DateTimeFormatter::format(dateTime, DateTimeFormat::RFC1123_FORMAT));
337}
338
339
340Poco::Timestamp MailMessage::getDate() const
341{
342 const std::string& dateTime = get(HEADER_DATE);
343 int tzd;
344 return DateTimeParser::parse(dateTime, tzd).timestamp();
345}
346
347
348bool MailMessage::isMultipart() const
349{
350 MediaType mediaType = getContentType();
351 return mediaType.matches("multipart");
352}
353
354
355void MailMessage::addPart(const std::string& name, const Poco::SharedPtr<PartSource>& pSource, ContentDisposition disposition, ContentTransferEncoding encoding)
356{
357 poco_check_ptr (pSource);
358
359 makeMultipart();
360 Part part;
361 part.name = name;
362 part.pSource = pSource;
363 part.disposition = disposition;
364 part.encoding = encoding;
365 _parts.push_back(part);
366}
367
368
369void MailMessage::addContent(const Poco::SharedPtr<PartSource>& pSource, ContentTransferEncoding encoding)
370{
371 addPart("", pSource, CONTENT_INLINE, encoding);
372}
373
374
375void MailMessage::addAttachment(const std::string& name, const Poco::SharedPtr<PartSource>& pSource, ContentTransferEncoding encoding)
376{
377 addPart(name, pSource, CONTENT_ATTACHMENT, encoding);
378}
379
380
381void MailMessage::read(std::istream& istr, PartHandler& handler)
382{
383 readHeader(istr);
384 if (isMultipart())
385 {
386 readMultipart(istr, handler);
387 }
388 else
389 {
390 StringPartHandler handler2(_content);
391 readPart(istr, *this, handler2);
392 }
393}
394
395
396void MailMessage::read(std::istream& istr)
397{
398 readHeader(istr);
399 if (isMultipart())
400 {
401 MultiPartHandler handler(this);
402 readMultipart(istr, handler);
403 }
404 else
405 {
406 StringPartHandler handler(_content);
407 readPart(istr, *this, handler);
408 }
409}
410
411
412void MailMessage::write(std::ostream& ostr) const
413{
414 MessageHeader header(*this);
415 setRecipientHeaders(header);
416 if (isMultipart())
417 {
418 writeMultipart(header, ostr);
419 }
420 else
421 {
422 writeHeader(header, ostr);
423 std::istringstream istr(_content);
424 writeEncoded(istr, ostr, _encoding);
425 }
426}
427
428
429void MailMessage::makeMultipart()
430{
431 if (!isMultipart())
432 {
433 MediaType mediaType("multipart", "mixed");
434 setContentType(mediaType);
435 }
436}
437
438
439void MailMessage::writeHeader(const MessageHeader& header, std::ostream& ostr) const
440{
441 header.write(ostr);
442 ostr << "\r\n";
443}
444
445
446void MailMessage::writeMultipart(MessageHeader& header, std::ostream& ostr) const
447{
448 if (_boundary.empty()) _boundary = MultipartWriter::createBoundary();
449 MediaType mediaType(getContentType());
450 mediaType.setParameter("boundary", _boundary);
451 header.set(HEADER_CONTENT_TYPE, mediaType.toString());
452 header.set(HEADER_MIME_VERSION, "1.0");
453 writeHeader(header, ostr);
454
455 MultipartWriter writer(ostr, _boundary);
456 for (PartVec::const_iterator it = _parts.begin(); it != _parts.end(); ++it)
457 {
458 writePart(writer, *it);
459 }
460 writer.close();
461}
462
463
464void MailMessage::writePart(MultipartWriter& writer, const Part& part)
465{
466 MessageHeader partHeader(part.pSource->headers());
467 MediaType mediaType(part.pSource->mediaType());
468 if (!part.name.empty())
469 mediaType.setParameter("name", part.name);
470 partHeader.set(HEADER_CONTENT_TYPE, mediaType.toString());
471 partHeader.set(HEADER_CONTENT_TRANSFER_ENCODING, contentTransferEncodingToString(part.encoding));
472 std::string disposition;
473 if (part.disposition == CONTENT_ATTACHMENT)
474 {
475 disposition = "attachment";
476 const std::string& filename = part.pSource->filename();
477 if (!filename.empty())
478 {
479 disposition.append("; filename=");
480 quote(filename, disposition);
481 }
482 }
483 else disposition = "inline";
484 partHeader.set(HEADER_CONTENT_DISPOSITION, disposition);
485 writer.nextPart(partHeader);
486 writeEncoded(part.pSource->stream(), writer.stream(), part.encoding);
487}
488
489
490void MailMessage::writeEncoded(std::istream& istr, std::ostream& ostr, ContentTransferEncoding encoding)
491{
492 switch (encoding)
493 {
494 case ENCODING_7BIT:
495 case ENCODING_8BIT:
496 StreamCopier::copyStream(istr, ostr);
497 break;
498 case ENCODING_QUOTED_PRINTABLE:
499 {
500 QuotedPrintableEncoder encoder(ostr);
501 StreamCopier::copyStream(istr, encoder);
502 encoder.close();
503 }
504 break;
505 case ENCODING_BASE64:
506 {
507 Base64Encoder encoder(ostr);
508 StreamCopier::copyStream(istr, encoder);
509 encoder.close();
510 }
511 break;
512 }
513}
514
515
516void MailMessage::readHeader(std::istream& istr)
517{
518 clear();
519 MessageHeader::RecipientList recipients;
520 MessageHeader::read(istr, &recipients);
521 istr.get(); // \r
522 if ('\n' == istr.peek()) istr.get(); // \n
523 for (const auto& r : recipients) addRecipient(r);
524}
525
526
527void MailMessage::readMultipart(std::istream& istr, PartHandler& handler)
528{
529 MediaType contentType(getContentType());
530 _boundary = contentType.getParameter("boundary");
531 MultipartReader reader(istr, _boundary);
532 while (reader.hasNextPart())
533 {
534 MessageHeader partHeader;
535 reader.nextPart(partHeader);
536 readPart(reader.stream(), partHeader, handler);
537 }
538}
539
540
541void MailMessage::readPart(std::istream& istr, const MessageHeader& header, PartHandler& handler)
542{
543 std::string encoding;
544 if (header.has(HEADER_CONTENT_TRANSFER_ENCODING))
545 {
546 encoding = header.get(HEADER_CONTENT_TRANSFER_ENCODING);
547 // get rid of a parameter if one is set
548 std::string::size_type pos = encoding.find(';');
549 if (pos != std::string::npos)
550 encoding.resize(pos);
551 }
552 if (icompare(encoding, CTE_QUOTED_PRINTABLE) == 0)
553 {
554 QuotedPrintableDecoder decoder(istr);
555 handlePart(decoder, header, handler);
556 _encoding = ENCODING_QUOTED_PRINTABLE;
557 }
558 else if (icompare(encoding, CTE_BASE64) == 0)
559 {
560 Base64Decoder decoder(istr);
561 handlePart(decoder, header, handler);
562 _encoding = ENCODING_BASE64;
563 }
564 else
565 {
566 if (icompare(encoding, CTE_7BIT) == 0)
567 _encoding = ENCODING_7BIT;
568 else if (icompare(encoding, CTE_8BIT) == 0)
569 _encoding = ENCODING_8BIT;
570
571 handlePart(istr, header, handler);
572 }
573}
574
575
576void MailMessage::handlePart(std::istream& istr, const MessageHeader& header, PartHandler& handler)
577{
578 handler.handlePart(header, istr);
579 // Read remaining characters from stream in case
580 // the handler failed to read the complete stream.
581 while (istr.good()) istr.get();
582}
583
584
585void MailMessage::setRecipientHeaders(MessageHeader& headers) const
586{
587 std::string to;
588 std::string cc;
589 std::string bcc;
590
591 for (Recipients::const_iterator it = _recipients.begin(); it != _recipients.end(); ++it)
592 {
593 switch (it->getType())
594 {
595 case MailRecipient::PRIMARY_RECIPIENT:
596 appendRecipient(*it, to);
597 break;
598 case MailRecipient::CC_RECIPIENT:
599 appendRecipient(*it, cc);
600 break;
601 case MailRecipient::BCC_RECIPIENT:
602 break;
603 }
604 }
605 if (!to.empty()) headers.set(HEADER_TO, to);
606 if (!cc.empty()) headers.set(HEADER_CC, cc);
607}
608
609
610const std::string& MailMessage::contentTransferEncodingToString(ContentTransferEncoding encoding)
611{
612 switch (encoding)
613 {
614 case ENCODING_7BIT:
615 return CTE_7BIT;
616 case ENCODING_8BIT:
617 return CTE_8BIT;
618 case ENCODING_QUOTED_PRINTABLE:
619 return CTE_QUOTED_PRINTABLE;
620 case ENCODING_BASE64:
621 return CTE_BASE64;
622 default:
623 poco_bugcheck();
624 }
625 return CTE_7BIT;
626}
627
628
629int MailMessage::lineLength(const std::string& str)
630{
631 int n = 0;
632 std::string::const_reverse_iterator it = str.rbegin();
633 std::string::const_reverse_iterator end = str.rend();
634 while (it != end && *it != '\n') { ++n; ++it; }
635 return n;
636}
637
638
639void MailMessage::appendRecipient(const MailRecipient& recipient, std::string& str)
640{
641 if (!str.empty()) str.append(", ");
642 const std::string& realName = recipient.getRealName();
643 const std::string& address = recipient.getAddress();
644 std::string rec;
645 if (!realName.empty())
646 {
647 quote(realName, rec, true);
648 rec.append(" ");
649 }
650 rec.append("<");
651 rec.append(address);
652 rec.append(">");
653 if (lineLength(str) + rec.length() > 70) str.append("\r\n\t");
654 str.append(rec);
655}
656
657
658void encodeQ(std::string& encodedText, std::string::const_iterator it, std::string::size_type& lineLength)
659{
660 switch (*it)
661 {
662 case ' ':
663 encodedText += '_';
664 lineLength++;
665 break;
666 case '=':
667 case '?':
668 case '_':
669 case '(':
670 case ')':
671 case '[':
672 case ']':
673 case '<':
674 case '>':
675 case ',':
676 case ';':
677 case ':':
678 case '.':
679 case '@':
680 encodedText += '=';
681 NumberFormatter::appendHex(encodedText, static_cast<unsigned>(static_cast<unsigned char>(*it)), 2);
682 lineLength += 3;
683 break;
684 default:
685 if (*it > 32 && *it < 127)
686 {
687 encodedText += *it;
688 lineLength++;
689 }
690 else
691 {
692 encodedText += '=';
693 NumberFormatter::appendHex(encodedText, static_cast<unsigned>(static_cast<unsigned char>(*it)), 2);
694 lineLength += 3;
695 }
696 }
697}
698
699
700void startEncoding(std::string& encodedText, const std::string& charset, char encoding)
701{
702 encodedText += "=?";
703 encodedText += charset;
704 encodedText += '?';
705 encodedText += encoding;
706 encodedText += '?';
707}
708
709
710std::string MailMessage::encodeWord(const std::string& text, const std::string& charset, char encoding)
711{
712 if (encoding == 'q' || encoding == 'Q')
713 {
714 bool containsNonASCII = false;
715 for (std::string::const_iterator it = text.begin(); it != text.end(); ++it)
716 {
717 if (static_cast<unsigned char>(*it) > 127)
718 {
719 containsNonASCII = true;
720 break;
721 }
722 }
723 if (!containsNonASCII) return text;
724 }
725
726 std::string encodedText;
727 std::string::size_type lineLength = 0;
728 if (encoding == 'q' || encoding == 'Q')
729 {
730 for (std::string::const_iterator it = text.begin(); it != text.end(); ++it)
731 {
732 if (lineLength == 0)
733 {
734 startEncoding(encodedText, charset, encoding);
735 lineLength += charset.length() + 5;
736 }
737 encodeQ(encodedText, it, lineLength);
738 if ((lineLength >= 64 &&
739 (*it == ' ' || *it == '\t' || *it == '\r' || *it == '\n')) ||
740 lineLength >= 72)
741 {
742 encodedText += "?=\r\n ";
743 lineLength = 0;
744 }
745 }
746 }
747 else if (encoding == 'b' || encoding == 'B')
748 {
749 // to ensure we're under 75 chars, 4 padding chars are always predicted
750 lineLength = 75 - (charset.length() + 5/*=??B?*/ + 2/*?=*/ + 4/*base64 padding*/);
751 std::string::size_type pos = 0;
752 size_t textLen = static_cast<size_t>(floor(lineLength * 3 / 4));
753 std::ostringstream ostr;
754 while (true)
755 {
756 Base64Encoder encoder(ostr);
757 encoder.rdbuf()->setLineLength(static_cast<int>(lineLength));
758 startEncoding(encodedText, charset, encoding);
759 std::string line = text.substr(pos, textLen);
760 encoder << line;
761 encoder.close();
762 encodedText.append(ostr.str());
763 encodedText.append("?=");
764 if (line.size() < textLen) break;
765 ostr.str("");
766 pos += textLen;
767 encodedText.append("\r\n");
768 }
769 lineLength = 0;;
770 }
771 else
772 {
773 throw InvalidArgumentException(Poco::format("MailMessage::encodeWord: "
774 "unknown encoding: %c", encoding));
775 }
776
777 if (lineLength > 0) encodedText += "?=";
778
779 return encodedText;
780}
781
782
783void MailMessage::advanceToEncoded(const std::string& encoded, std::string& decoded, std::string::size_type& pos1, bool& isComment)
784{
785 bool spaceOnly = isComment; // flag to trim away spaces between encoded-word's
786 auto it = encoded.begin();
787 auto end = encoded.end();
788 for (; it != end; ++it)
789 {
790 if (*it == '=')
791 {
792 if (++it != end && *it == '?')
793 {
794 if (spaceOnly) trimRightInPlace(decoded);
795 return;
796 }
797 }
798 else if (*it == '(') isComment = true;
799 else if (*it == ')') isComment = false;
800 if ((isComment) && (!Ascii::isSpace(*it))) spaceOnly = false;
801 decoded.append(1, *it);
802 ++pos1;
803 }
804 pos1 = std::string::npos;
805}
806
807
808std::string MailMessage::decodeWord(const std::string& encoded, std::string toCharset)
809{
810 std::string encodedWord = replace(encoded, "?=\r\n=?", "?==?");
811 bool toCharsetGiven = !toCharset.empty();
812 std::string errMsg;
813 const std::size_t notFound = std::string::npos;
814 std::string decoded;
815 std::string::size_type pos1 = 0, pos2 = 0;
816 bool isComment = false;
817 advanceToEncoded(encodedWord, decoded, pos1, isComment);
818 if (pos1 != notFound)
819 {
820 getEncWordLimits(encodedWord, pos1, pos2, isComment);
821 while ((pos1 != notFound) && (pos2 != notFound) && pos2 > pos1 + 2)
822 {
823 pos1 += 2;
824 StringTokenizer st(encodedWord.substr(pos1, pos2 - pos1), "?");
825 if (st.count() == 3)
826 {
827 std::string charset = st[0];
828 if (!toCharsetGiven) toCharset = charset;
829 if (st[1].size() > 1)
830 {
831 throw InvalidArgumentException(Poco::format("MailMessage::decodeWord: "
832 "invalid encoding %s", st[1]));
833 }
834 char encoding = st[1][0];
835 std::string encodedText = st[2];
836 if (encodedText.find_first_of(" ?") != notFound)
837 {
838 throw InvalidArgumentException("MailMessage::decodeWord: "
839 "forbidden characters found in encoded-word");
840 }
841 else if (encoding == 'q' || encoding == 'Q')
842 {
843 // no incomplete encoded characters allowed on single line
844 std::string::size_type eqPos = encodedText.rfind('=');
845 if (eqPos != notFound)
846 {
847 if ((eqPos + 2) >= encodedText.size())
848 {
849 throw InvalidArgumentException("MailMessage::decodeWord: "
850 "incomplete encoded character found in encoded-word");
851 }
852 }
853 }
854 decoded.append(decodeWord(charset, encoding, encodedText, toCharset));
855 pos1 = pos2 + 2;
856 advanceToEncoded(encodedWord.substr(pos1), decoded, pos1, isComment);
857 if (pos1 != notFound) getEncWordLimits(encodedWord, pos1, pos2, isComment);
858 }
859 else
860 {
861 throw InvalidArgumentException(Poco::format("MailMessage::decodeWord: "
862 "invalid number of entries in encoded-word (expected 3, found %z)", st.count()));
863 }
864 }
865 }
866 else decoded = std::move(encodedWord);
867 return decoded;
868}
869
870
871void MailMessage::getEncWordLimits(const std::string& encodedWord, std::string::size_type& pos1, std::string::size_type& pos2, bool isComment)
872{
873 const std::size_t notFound = std::string::npos;
874
875 pos1 = encodedWord.find("=?", pos1); // beginning of encoded-word
876 if (pos1 != notFound)
877 {
878 // must look sequentially for all '?' occurences because of a (valid) case like this:
879 // =?ISO-8859-1?q?=C4?=
880 // where end would be prematurely found if we search for ?= only
881 pos2 = encodedWord.find('?', pos1 + 2); // first '?'
882 if (pos2 == notFound) goto err;
883 pos2 = encodedWord.find('?', pos2 + 1); // second '?'
884 if (pos2 == notFound) goto err;
885 pos2 = encodedWord.find("?=", pos2 + 1); // end of encoded-word
886 if (pos2 == notFound) goto err;
887 // before we leave, double-check for the next encoded-word end, to make sure
888 // an illegal '?' was not sneaked in (eg. =?ISO-8859-1?q?=C4?=D6?=)
889 if (((encodedWord.find("?=", pos2 + 1) != notFound &&
890 encodedWord.find("=?", pos2 + 1) == notFound)) ||
891 ((encodedWord.find("=?", pos2 + 1) != notFound &&
892 encodedWord.find("?=", pos2 + 1) == notFound))) goto err;
893 }
894 else goto err;
895
896 // if encoded word is in a comment, then '(' and ')' are forbidden inside it
897 if (isComment &&
898 (notFound != encodedWord.substr(pos1, pos2 - pos1).find_first_of("()"))) goto err;
899
900 return;
901
902err:
903 throw InvalidArgumentException("MailMessage::encodedWordLimits: invalid encoded word");
904}
905
906
907std::string MailMessage::decodeWord(const std::string& charset, char encoding,
908 const std::string& text, const std::string& toCharset)
909{
910 const TextEncodingRegistry& registry = TextEncoding::registry();
911 if (!registry.has(charset) || !registry.has(toCharset))
912 {
913 throw NotImplementedException(Poco::format("MailMessage::decodeWord: "
914 "charset not supported: %s", charset));
915 }
916 TextEncoding* fromEnc = registry.find(charset);
917 TextEncoding* toEnc = registry.find(toCharset);
918
919 std::string decoded;
920 switch (encoding)
921 {
922 case 'B': case 'b':
923 {
924 std::istringstream istr(text);
925 Base64Decoder decoder(istr);
926 int c = decoder.get();
927 while (c != -1) { decoded.append(1, char(c)); c = decoder.get(); }
928 break;
929 }
930 case 'Q': case 'q':
931 {
932 bool isWide = false;
933 std::vector<char> wideChar;
934 std::vector<unsigned char> wideCharSeq;
935 for (const auto& c : text)
936 {
937 if (!Ascii::isPrintable(c) || c == '?' || c == ' ')
938 {
939 throw InvalidArgumentException("MailMessage::decodeWord: encoded-word must not contain "
940 "non-printable characters, '? or SPACE");
941 }
942 if (c == '_') decoded.append(1, ' ');
943 else if (c == '=') isWide = true;
944 else if (isWide)
945 {
946 wideChar.push_back(c);
947 if (wideChar.size() % 2 == 0)
948 {
949 std::string wcStr(&wideChar[0], wideChar.size());
950 UInt8 chr = static_cast<UInt8>(NumberParser::parseHex(wcStr));
951 wideCharSeq.push_back(chr);
952 if (fromEnc->sequenceLength(&wideCharSeq[0], static_cast<int>(wideCharSeq.size())) > 0)
953 {
954 auto it = wideCharSeq.begin();
955 auto end = wideCharSeq.end();
956 for (; it != end; ++it)
957 {
958 decoded.append(1, static_cast<char>(*it));
959 }
960 wideChar.clear();
961 wideCharSeq.clear();
962 isWide = false;
963 }
964 }
965 }
966 else decoded.append(1, c);
967 }
968 break;
969 }
970 default:
971 throw InvalidArgumentException(Poco::format("MailMessage::decodeWord: Unknown encoding: %c", encoding));
972 }
973 if (charset != toCharset)
974 {
975 TextConverter converter(*fromEnc, *toEnc);
976 std::string converted;
977 converter.convert(decoded, converted);
978 return converted;
979 }
980 return decoded;
981}
982
983
984PartSource* MailMessage::createPartStore(const std::string& content, const std::string& mediaType, const std::string& filename)
985{
986 if (!_pStoreFactory) return new StringPartSource(content, mediaType, filename);
987 else return _pStoreFactory->createPartStore(content, mediaType, filename);
988}
989
990
991MultipartSource::MultipartSource(const std::string& contentType):
992 PartSource(contentTypeWithBoundary(contentType))
993{
994}
995
996
997MultipartSource::~MultipartSource()
998{
999}
1000
1001
1002void MultipartSource::addPart(const std::string& name,
1003 const Poco::SharedPtr<PartSource>& pSource,
1004 MailMessage::ContentDisposition disposition,
1005 MailMessage::ContentTransferEncoding encoding)
1006{
1007 MailMessage::Part part;
1008 part.name = name;
1009 part.pSource = pSource;
1010 part.disposition = disposition;
1011 part.encoding = encoding;
1012 _parts.push_back(part);
1013}
1014
1015
1016std::istream& MultipartSource::stream()
1017{
1018 MediaType mt(mediaType());
1019 std::string boundary = mt.getParameter("boundary");
1020
1021 MultipartWriter writer(_content, boundary);
1022 for (MailMessage::PartVec::const_iterator it = _parts.begin(); it != _parts.end(); ++it)
1023 {
1024 MailMessage::writePart(writer, *it);
1025 }
1026 writer.close();
1027
1028 return _content;
1029}
1030
1031
1032std::string MultipartSource::contentTypeWithBoundary(const std::string& contentType)
1033{
1034 MediaType mediaType(contentType);
1035 mediaType.setParameter("boundary", MultipartWriter::createBoundary());
1036 return mediaType.toString();
1037}
1038
1039
1040} } // namespace Poco::Net
1041