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/StringTokenizer.h"
33#include "Poco/StreamCopier.h"
34#include "Poco/NumberFormatter.h"
35#include <sstream>
36
37
38using Poco::Base64Encoder;
39using Poco::Base64Decoder;
40using Poco::StreamCopier;
41using Poco::DateTimeFormat;
42using Poco::DateTimeFormatter;
43using Poco::DateTimeParser;
44using Poco::StringTokenizer;
45using Poco::icompare;
46
47
48namespace Poco {
49namespace Net {
50
51
52namespace
53{
54 class MultiPartHandler: public PartHandler
55 /// This is a default part handler for multipart messages, used when there
56 /// is no external handler provided to he MailMessage. This handler
57 /// will handle all types of message parts, including attachments.
58 {
59 public:
60 MultiPartHandler(MailMessage* pMsg): _pMsg(pMsg)
61 /// Creates multi part handler.
62 /// The pMsg pointer points to the calling MailMessage
63 /// and will be used to properly populate it, so the
64 /// message content could be written out unmodified
65 /// in its entirety, including attachments.
66 {
67 }
68
69 ~MultiPartHandler()
70 /// Destroys string part handler.
71 {
72 }
73
74 void handlePart(const MessageHeader& header, std::istream& stream)
75 /// Handles a part. If message pointer was provided at construction time,
76 /// the message pointed to will be properly populated so it could be written
77 /// back out at a later point in time.
78 {
79 std::string tmp;
80 Poco::StreamCopier::copyToString(stream, tmp);
81 if (_pMsg)
82 {
83 MailMessage::ContentTransferEncoding cte = MailMessage::ENCODING_7BIT;
84 if (header.has(MailMessage::HEADER_CONTENT_TRANSFER_ENCODING))
85 {
86 std::string enc = header[MailMessage::HEADER_CONTENT_TRANSFER_ENCODING];
87 if (enc == MailMessage::CTE_8BIT)
88 cte = MailMessage::ENCODING_8BIT;
89 else if (enc == MailMessage::CTE_QUOTED_PRINTABLE)
90 cte = MailMessage::ENCODING_QUOTED_PRINTABLE;
91 else if (enc == MailMessage::CTE_BASE64)
92 cte = MailMessage::ENCODING_BASE64;
93 }
94
95 std::string contentType = header.get(MailMessage::HEADER_CONTENT_TYPE, "text/plain; charset=us-ascii");
96 std::string contentDisp = header.get(MailMessage::HEADER_CONTENT_DISPOSITION, "");
97 std::string filename;
98 if (!contentDisp.empty())
99 filename = getParamFromHeader(contentDisp, "filename");
100 if (filename.empty())
101 filename = getParamFromHeader(contentType, "name");
102 PartSource* pPS = _pMsg->createPartStore(tmp, contentType, filename);
103 poco_check_ptr (pPS);
104 NameValueCollection::ConstIterator it = header.begin();
105 NameValueCollection::ConstIterator end = header.end();
106 bool added = false;
107 for (; it != end; ++it)
108 {
109 if (!added && MailMessage::HEADER_CONTENT_DISPOSITION == it->first)
110 {
111 if (it->second == "inline")
112 _pMsg->addContent(pPS, cte);
113 else
114 _pMsg->addAttachment(getPartName(header), pPS, cte);
115 added = true;
116 }
117
118 pPS->headers().set(it->first, it->second);
119 }
120
121 if (contentDisp.empty())
122 {
123 _pMsg->addContent(pPS, cte);
124 added = true;
125 }
126
127 if (!added) delete pPS;
128 }
129 }
130
131 private:
132 std::string getParamFromHeader(const std::string& header, const std::string& param)
133 {
134 StringTokenizer st(header, ";=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
135 StringTokenizer::Iterator it = st.begin();
136 StringTokenizer::Iterator end = st.end();
137 for (; it != end; ++it) { if (icompare(*it, param) == 0) break; }
138 if (it != end)
139 {
140 ++it;
141 if (it == end) return "";
142 return *it;
143 }
144 return "";
145 }
146
147 std::string getPartName(const MessageHeader& header)
148 {
149 std::string ct = MailMessage::HEADER_CONTENT_TYPE;
150 if (header.has(ct))
151 return getParamFromHeader(header[ct], "name");
152 return "";
153 }
154
155 MailMessage* _pMsg;
156 };
157
158
159 class StringPartHandler: public PartHandler
160 /// This is a default part handler, used when there is no
161 /// external handler provided to the MailMessage. This handler
162 /// handles only single-part messages.
163 {
164 public:
165 StringPartHandler(std::string& content): _str(content)
166 /// Creates string part handler.
167 /// The content parameter represents the part content.
168 {
169 }
170
171 ~StringPartHandler()
172 /// Destroys string part handler.
173 {
174 }
175
176 void handlePart(const MessageHeader& header, std::istream& stream)
177 /// Handles a part.
178 {
179 std::string tmp;
180 Poco::StreamCopier::copyToString(stream, tmp);
181 _str.append(tmp);
182 }
183
184 private:
185 std::string& _str;
186 };
187}
188
189
190const std::string MailMessage::HEADER_SUBJECT("Subject");
191const std::string MailMessage::HEADER_FROM("From");
192const std::string MailMessage::HEADER_TO("To");
193const std::string MailMessage::HEADER_CC("CC");
194const std::string MailMessage::HEADER_BCC("BCC");
195const std::string MailMessage::HEADER_DATE("Date");
196const std::string MailMessage::HEADER_CONTENT_TYPE("Content-Type");
197const std::string MailMessage::HEADER_CONTENT_TRANSFER_ENCODING("Content-Transfer-Encoding");
198const std::string MailMessage::HEADER_CONTENT_DISPOSITION("Content-Disposition");
199const std::string MailMessage::HEADER_CONTENT_ID("Content-ID");
200const std::string MailMessage::HEADER_MIME_VERSION("Mime-Version");
201const std::string MailMessage::EMPTY_HEADER;
202const std::string MailMessage::TEXT_PLAIN("text/plain");
203const std::string MailMessage::CTE_7BIT("7bit");
204const std::string MailMessage::CTE_8BIT("8bit");
205const std::string MailMessage::CTE_QUOTED_PRINTABLE("quoted-printable");
206const std::string MailMessage::CTE_BASE64("base64");
207
208
209MailMessage::MailMessage(PartStoreFactory* pStoreFactory):
210 _encoding(),
211 _pStoreFactory(pStoreFactory)
212{
213 Poco::Timestamp now;
214 setDate(now);
215 setContentType("text/plain");
216}
217
218
219MailMessage::~MailMessage()
220{
221 for (PartVec::iterator it = _parts.begin(); it != _parts.end(); ++it)
222 {
223 delete it->pSource;
224 }
225}
226
227
228void MailMessage::addRecipient(const MailRecipient& recipient)
229{
230 _recipients.push_back(recipient);
231}
232
233
234void MailMessage::setRecipients(const Recipients& recipients)
235{
236 _recipients.assign(recipients.begin(), recipients.end());
237}
238
239
240void MailMessage::setSender(const std::string& sender)
241{
242 set(HEADER_FROM, sender);
243}
244
245
246const std::string& MailMessage::getSender() const
247{
248 if (has(HEADER_FROM))
249 return get(HEADER_FROM);
250 else
251 return EMPTY_HEADER;
252}
253
254
255void MailMessage::setSubject(const std::string& subject)
256{
257 set(HEADER_SUBJECT, subject);
258}
259
260
261const std::string& MailMessage::getSubject() const
262{
263 if (has(HEADER_SUBJECT))
264 return get(HEADER_SUBJECT);
265 else
266 return EMPTY_HEADER;
267}
268
269
270void MailMessage::setContent(const std::string& content, ContentTransferEncoding encoding)
271{
272 _content = content;
273 _encoding = encoding;
274 set(HEADER_CONTENT_TRANSFER_ENCODING, contentTransferEncodingToString(encoding));
275}
276
277
278void MailMessage::setContentType(const std::string& mediaType)
279{
280 set(HEADER_CONTENT_TYPE, mediaType);
281}
282
283
284void MailMessage::setContentType(const MediaType& mediaType)
285{
286 setContentType(mediaType.toString());
287}
288
289
290const std::string& MailMessage::getContentType() const
291{
292 if (has(HEADER_CONTENT_TYPE))
293 return get(HEADER_CONTENT_TYPE);
294 else
295 return TEXT_PLAIN;
296}
297
298
299void MailMessage::setDate(const Poco::Timestamp& dateTime)
300{
301 set(HEADER_DATE, DateTimeFormatter::format(dateTime, DateTimeFormat::RFC1123_FORMAT));
302}
303
304
305Poco::Timestamp MailMessage::getDate() const
306{
307 const std::string& dateTime = get(HEADER_DATE);
308 int tzd;
309 return DateTimeParser::parse(dateTime, tzd).timestamp();
310}
311
312
313bool MailMessage::isMultipart() const
314{
315 MediaType mediaType = getContentType();
316 return mediaType.matches("multipart");
317}
318
319
320void MailMessage::addPart(const std::string& name, PartSource* pSource, ContentDisposition disposition, ContentTransferEncoding encoding)
321{
322 poco_check_ptr (pSource);
323
324 makeMultipart();
325 Part part;
326 part.name = name;
327 part.pSource = pSource;
328 part.disposition = disposition;
329 part.encoding = encoding;
330 _parts.push_back(part);
331}
332
333
334void MailMessage::addContent(PartSource* pSource, ContentTransferEncoding encoding)
335{
336 addPart("", pSource, CONTENT_INLINE, encoding);
337}
338
339
340void MailMessage::addAttachment(const std::string& name, PartSource* pSource, ContentTransferEncoding encoding)
341{
342 addPart(name, pSource, CONTENT_ATTACHMENT, encoding);
343}
344
345
346void MailMessage::read(std::istream& istr, PartHandler& handler)
347{
348 readHeader(istr);
349 if (isMultipart())
350 {
351 readMultipart(istr, handler);
352 }
353 else
354 {
355 StringPartHandler handler(_content);
356 readPart(istr, *this, handler);
357 }
358}
359
360
361void MailMessage::read(std::istream& istr)
362{
363 readHeader(istr);
364 if (isMultipart())
365 {
366 MultiPartHandler handler(this);
367 readMultipart(istr, handler);
368 }
369 else
370 {
371 StringPartHandler handler(_content);
372 readPart(istr, *this, handler);
373 }
374}
375
376
377void MailMessage::write(std::ostream& ostr) const
378{
379 MessageHeader header(*this);
380 setRecipientHeaders(header);
381 if (isMultipart())
382 {
383 writeMultipart(header, ostr);
384 }
385 else
386 {
387 writeHeader(header, ostr);
388 std::istringstream istr(_content);
389 writeEncoded(istr, ostr, _encoding);
390 }
391}
392
393
394void MailMessage::makeMultipart()
395{
396 if (!isMultipart())
397 {
398 MediaType mediaType("multipart", "mixed");
399 setContentType(mediaType);
400 }
401}
402
403
404void MailMessage::writeHeader(const MessageHeader& header, std::ostream& ostr) const
405{
406 header.write(ostr);
407 ostr << "\r\n";
408}
409
410
411void MailMessage::writeMultipart(MessageHeader& header, std::ostream& ostr) const
412{
413 if (_boundary.empty()) _boundary = MultipartWriter::createBoundary();
414 MediaType mediaType(getContentType());
415 mediaType.setParameter("boundary", _boundary);
416 header.set(HEADER_CONTENT_TYPE, mediaType.toString());
417 header.set(HEADER_MIME_VERSION, "1.0");
418 writeHeader(header, ostr);
419
420 MultipartWriter writer(ostr, _boundary);
421 for (PartVec::const_iterator it = _parts.begin(); it != _parts.end(); ++it)
422 {
423 writePart(writer, *it);
424 }
425 writer.close();
426}
427
428
429void MailMessage::writePart(MultipartWriter& writer, const Part& part) const
430{
431 MessageHeader partHeader(part.pSource->headers());
432 MediaType mediaType(part.pSource->mediaType());
433 if (!part.name.empty())
434 mediaType.setParameter("name", part.name);
435 partHeader.set(HEADER_CONTENT_TYPE, mediaType.toString());
436 partHeader.set(HEADER_CONTENT_TRANSFER_ENCODING, contentTransferEncodingToString(part.encoding));
437 std::string disposition;
438 if (part.disposition == CONTENT_ATTACHMENT)
439 {
440 disposition = "attachment";
441 const std::string& filename = part.pSource->filename();
442 if (!filename.empty())
443 {
444 disposition.append("; filename=");
445 quote(filename, disposition);
446 }
447 }
448 else disposition = "inline";
449 partHeader.set(HEADER_CONTENT_DISPOSITION, disposition);
450 writer.nextPart(partHeader);
451 writeEncoded(part.pSource->stream(), writer.stream(), part.encoding);
452}
453
454
455void MailMessage::writeEncoded(std::istream& istr, std::ostream& ostr, ContentTransferEncoding encoding) const
456{
457 switch (encoding)
458 {
459 case ENCODING_7BIT:
460 case ENCODING_8BIT:
461 StreamCopier::copyStream(istr, ostr);
462 break;
463 case ENCODING_QUOTED_PRINTABLE:
464 {
465 QuotedPrintableEncoder encoder(ostr);
466 StreamCopier::copyStream(istr, encoder);
467 encoder.close();
468 }
469 break;
470 case ENCODING_BASE64:
471 {
472 Base64Encoder encoder(ostr);
473 StreamCopier::copyStream(istr, encoder);
474 encoder.close();
475 }
476 break;
477 }
478}
479
480
481void MailMessage::readHeader(std::istream& istr)
482{
483 clear();
484 MessageHeader::RecipientList recipients;
485 MessageHeader::read(istr, &recipients);
486 istr.get(); // \r
487 if ('\n' == istr.peek()) istr.get(); // \n
488 for (const auto& r : recipients) addRecipient(r);
489}
490
491
492void MailMessage::readMultipart(std::istream& istr, PartHandler& handler)
493{
494 MediaType contentType(getContentType());
495 _boundary = contentType.getParameter("boundary");
496 MultipartReader reader(istr, _boundary);
497 while (reader.hasNextPart())
498 {
499 MessageHeader partHeader;
500 reader.nextPart(partHeader);
501 readPart(reader.stream(), partHeader, handler);
502 }
503}
504
505
506void MailMessage::readPart(std::istream& istr, const MessageHeader& header, PartHandler& handler)
507{
508 std::string encoding;
509 if (header.has(HEADER_CONTENT_TRANSFER_ENCODING))
510 {
511 encoding = header.get(HEADER_CONTENT_TRANSFER_ENCODING);
512 // get rid of a parameter if one is set
513 std::string::size_type pos = encoding.find(';');
514 if (pos != std::string::npos)
515 encoding.resize(pos);
516 }
517 if (icompare(encoding, CTE_QUOTED_PRINTABLE) == 0)
518 {
519 QuotedPrintableDecoder decoder(istr);
520 handlePart(decoder, header, handler);
521 _encoding = ENCODING_QUOTED_PRINTABLE;
522 }
523 else if (icompare(encoding, CTE_BASE64) == 0)
524 {
525 Base64Decoder decoder(istr);
526 handlePart(decoder, header, handler);
527 _encoding = ENCODING_BASE64;
528 }
529 else
530 {
531 if (icompare(encoding, CTE_7BIT) == 0)
532 _encoding = ENCODING_7BIT;
533 else if (icompare(encoding, CTE_8BIT) == 0)
534 _encoding = ENCODING_8BIT;
535
536 handlePart(istr, header, handler);
537 }
538}
539
540
541void MailMessage::handlePart(std::istream& istr, const MessageHeader& header, PartHandler& handler)
542{
543 handler.handlePart(header, istr);
544 // Read remaining characters from stream in case
545 // the handler failed to read the complete stream.
546 while (istr.good()) istr.get();
547}
548
549
550void MailMessage::setRecipientHeaders(MessageHeader& headers) const
551{
552 std::string to;
553 std::string cc;
554 std::string bcc;
555
556 for (Recipients::const_iterator it = _recipients.begin(); it != _recipients.end(); ++it)
557 {
558 switch (it->getType())
559 {
560 case MailRecipient::PRIMARY_RECIPIENT:
561 appendRecipient(*it, to);
562 break;
563 case MailRecipient::CC_RECIPIENT:
564 appendRecipient(*it, cc);
565 break;
566 case MailRecipient::BCC_RECIPIENT:
567 break;
568 }
569 }
570 if (!to.empty()) headers.set(HEADER_TO, to);
571 if (!cc.empty()) headers.set(HEADER_CC, cc);
572}
573
574
575const std::string& MailMessage::contentTransferEncodingToString(ContentTransferEncoding encoding)
576{
577 switch (encoding)
578 {
579 case ENCODING_7BIT:
580 return CTE_7BIT;
581 case ENCODING_8BIT:
582 return CTE_8BIT;
583 case ENCODING_QUOTED_PRINTABLE:
584 return CTE_QUOTED_PRINTABLE;
585 case ENCODING_BASE64:
586 return CTE_BASE64;
587 default:
588 poco_bugcheck();
589 }
590 return CTE_7BIT;
591}
592
593
594int MailMessage::lineLength(const std::string& str)
595{
596 int n = 0;
597 std::string::const_reverse_iterator it = str.rbegin();
598 std::string::const_reverse_iterator end = str.rend();
599 while (it != end && *it != '\n') { ++n; ++it; }
600 return n;
601}
602
603
604void MailMessage::appendRecipient(const MailRecipient& recipient, std::string& str)
605{
606 if (!str.empty()) str.append(", ");
607 const std::string& realName = recipient.getRealName();
608 const std::string& address = recipient.getAddress();
609 std::string rec;
610 if (!realName.empty())
611 {
612 quote(realName, rec, true);
613 rec.append(" ");
614 }
615 rec.append("<");
616 rec.append(address);
617 rec.append(">");
618 if (lineLength(str) + rec.length() > 70) str.append("\r\n\t");
619 str.append(rec);
620}
621
622
623std::string MailMessage::encodeWord(const std::string& text, const std::string& charset)
624{
625 bool containsNonASCII = false;
626 for (std::string::const_iterator it = text.begin(); it != text.end(); ++it)
627 {
628 if (static_cast<unsigned char>(*it) > 127)
629 {
630 containsNonASCII = true;
631 break;
632 }
633 }
634 if (!containsNonASCII) return text;
635
636 std::string encodedText;
637 std::string::size_type lineLength = 0;
638 for (std::string::const_iterator it = text.begin(); it != text.end(); ++it)
639 {
640 if (lineLength == 0)
641 {
642 encodedText += "=?";
643 encodedText += charset;
644 encodedText += "?q?";
645 lineLength += charset.length() + 5;
646 }
647 switch (*it)
648 {
649 case ' ':
650 encodedText += '_';
651 lineLength++;
652 break;
653 case '=':
654 case '?':
655 case '_':
656 case '(':
657 case ')':
658 case '[':
659 case ']':
660 case '<':
661 case '>':
662 case ',':
663 case ';':
664 case ':':
665 case '.':
666 case '@':
667 encodedText += '=';
668 NumberFormatter::appendHex(encodedText, static_cast<unsigned>(static_cast<unsigned char>(*it)), 2);
669 lineLength += 3;
670 break;
671 default:
672 if (*it > 32 && *it < 127)
673 {
674 encodedText += *it;
675 lineLength++;
676 }
677 else
678 {
679 encodedText += '=';
680 NumberFormatter::appendHex(encodedText, static_cast<unsigned>(static_cast<unsigned char>(*it)), 2);
681 lineLength += 3;
682 }
683 }
684 if ((lineLength >= 64 && (*it == ' ' || *it == '\t' || *it == '\r' || *it == '\n')) || lineLength >= 72)
685 {
686 encodedText += "?=\r\n ";
687 lineLength = 0;
688 }
689 }
690 if (lineLength > 0)
691 {
692 encodedText += "?=";
693 }
694 return encodedText;
695}
696
697
698PartSource* MailMessage::createPartStore(const std::string& content, const std::string& mediaType, const std::string& filename)
699{
700 if (!_pStoreFactory) return new StringPartSource(content, mediaType, filename);
701 else return _pStoreFactory->createPartStore(content, mediaType, filename);
702}
703
704
705} } // namespace Poco::Net
706