diff --git a/CHANGELOG b/CHANGELOG index 37b143ac5..61734faca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ Release 1.5.2 (2013-03-??) - fixed GH #118: JSON::Object::stringify endless loop - added Recursive and SortedDirectoryIterator (Marian Krivos) - added ListMap (map-like container with preserving insertion order) +- MailMessage: attachments saving support and consistent read/write Release 1.5.1 (2013-01-11) ========================== diff --git a/Net/Net_CE_vs90.vcproj b/Net/Net_CE_vs90.vcproj index 47a58b2ec..f3bc53560 100644 --- a/Net/Net_CE_vs90.vcproj +++ b/Net/Net_CE_vs90.vcproj @@ -554,6 +554,8 @@ RelativePath=".\include\Poco\Net\PartHandler.h"/> + + + @@ -374,6 +375,7 @@ + diff --git a/Net/Net_vs100.vcxproj.filters b/Net/Net_vs100.vcxproj.filters index 5b0a5cf62..3d2e72cae 100644 --- a/Net/Net_vs100.vcxproj.filters +++ b/Net/Net_vs100.vcxproj.filters @@ -423,6 +423,9 @@ NetCore\Header Files + + Messages\Header Files + @@ -701,6 +704,9 @@ NetCore\Source Files + + Messages\Source Files + diff --git a/Net/Net_vs110.vcxproj b/Net/Net_vs110.vcxproj index 7d99b806d..43fc6dbe7 100644 --- a/Net/Net_vs110.vcxproj +++ b/Net/Net_vs110.vcxproj @@ -304,6 +304,7 @@ + @@ -403,6 +404,7 @@ + diff --git a/Net/Net_vs110.vcxproj.filters b/Net/Net_vs110.vcxproj.filters index 7bca18a6b..b6e78d737 100644 --- a/Net/Net_vs110.vcxproj.filters +++ b/Net/Net_vs110.vcxproj.filters @@ -222,6 +222,9 @@ Messages\Header Files + + Messages\Header Files + Messages\Header Files @@ -515,6 +518,9 @@ Messages\Source Files + + Messages\Source Files + Messages\Source Files diff --git a/Net/Net_vs71.vcproj b/Net/Net_vs71.vcproj index 440c65da4..4ce770675 100644 --- a/Net/Net_vs71.vcproj +++ b/Net/Net_vs71.vcproj @@ -486,6 +486,8 @@ RelativePath=".\include\Poco\Net\PartHandler.h"/> + + + + + + + @@ -395,6 +396,7 @@ + diff --git a/Net/Net_x64_vs100.vcxproj.filters b/Net/Net_x64_vs100.vcxproj.filters index 392d423af..f86ba9bfe 100644 --- a/Net/Net_x64_vs100.vcxproj.filters +++ b/Net/Net_x64_vs100.vcxproj.filters @@ -219,6 +219,9 @@ Messages\Header Files + + Messages\Header Files + Messages\Header Files @@ -512,6 +515,9 @@ Messages\Source Files + + Messages\Source Files + Messages\Source Files diff --git a/Net/Net_x64_vs110.vcxproj b/Net/Net_x64_vs110.vcxproj index 810f6dfc9..f68514ef3 100644 --- a/Net/Net_x64_vs110.vcxproj +++ b/Net/Net_x64_vs110.vcxproj @@ -302,6 +302,7 @@ + @@ -401,6 +402,7 @@ + diff --git a/Net/Net_x64_vs110.vcxproj.filters b/Net/Net_x64_vs110.vcxproj.filters index 18dd8f2dd..3f97fdc73 100644 --- a/Net/Net_x64_vs110.vcxproj.filters +++ b/Net/Net_x64_vs110.vcxproj.filters @@ -222,6 +222,9 @@ Messages\Header Files + + Messages\Header Files + Messages\Header Files @@ -515,6 +518,9 @@ Messages\Source Files + + Messages\Source Files + Messages\Source Files diff --git a/Net/Net_x64_vs90.vcproj b/Net/Net_x64_vs90.vcproj index d087f6a66..9e42a53fa 100644 --- a/Net/Net_x64_vs90.vcproj +++ b/Net/Net_x64_vs90.vcproj @@ -511,6 +511,8 @@ RelativePath=".\include\Poco\Net\PartHandler.h"/> + + @@ -86,12 +87,28 @@ public: ENCODING_BASE64 }; - MailMessage(); + struct Part + { + std::string name; + PartSource* pSource; + ContentDisposition disposition; + ContentTransferEncoding encoding; + }; + + typedef std::vector PartVec; + + MailMessage(PartStoreFactory* pStoreFactory = 0); /// Creates an empty MailMessage. + /// + /// If pStoreFactory is not null, message attachments will be + /// handled by the object created by the factory. Most + /// common reason is to temporarily save attachments to + /// the file system in order to avoid potential memory + /// exhaustion when attachment files are very large. virtual ~MailMessage(); /// Destroys the MailMessage. - + void addRecipient(const MailRecipient& recipient); /// Adds a recipient for the message. @@ -164,7 +181,10 @@ public: bool isMultipart() const; /// Returns true iff the message is a multipart message. - void addPart(const std::string& name, PartSource* pSource, ContentDisposition disposition, ContentTransferEncoding encoding); + void addPart(const std::string& name, + PartSource* pSource, + ContentDisposition disposition, + ContentTransferEncoding encoding); /// Adds a part/attachment to the mail message. /// /// The MailMessage takes ownership of the PartSource and deletes it @@ -178,7 +198,8 @@ public: /// To include non-ASCII characters in the part name or filename, /// use RFC 2047 word encoding (see encodeWord()). - void addContent(PartSource* pSource, ContentTransferEncoding encoding = ENCODING_QUOTED_PRINTABLE); + void addContent(PartSource* pSource, + ContentTransferEncoding encoding = ENCODING_QUOTED_PRINTABLE); /// Adds a part to the mail message by calling /// addPart("", pSource, CONTENT_INLINE, encoding); /// @@ -186,8 +207,10 @@ public: /// must not contain any non-ASCII characters. /// To include non-ASCII characters in the part name or filename, /// use RFC 2047 word encoding (see encodeWord()). - - void addAttachment(const std::string& name, PartSource* pSource, ContentTransferEncoding encoding = ENCODING_BASE64); + + void addAttachment(const std::string& name, + PartSource* pSource, + ContentTransferEncoding encoding = ENCODING_BASE64); /// Adds an attachment to the mail message by calling /// addPart(name, pSource, CONTENT_ATTACHMENT, encoding); /// @@ -196,6 +219,19 @@ public: /// To include non-ASCII characters in the part name or filename, /// use RFC 2047 word encoding (see encodeWord()). + PartSource* getPartStore(const std::string& content, + const std::string& mediaType, + const std::string& filename = ""); + /// Returns either built-in default (StringPartSource) part store or, + /// if the part store factory was provided during contruction, + /// the one created by PartStoreFactory. + /// Returned part store is allocated on the heap; it is caller's responsibility + /// to delete it after use. Typical use is handler passing it back to MailMessage, + /// which takes care of the cleanup. + + const PartVec& parts() const; + /// Returns const reference to the vector containing part stores. + void read(std::istream& istr, PartHandler& handler); /// Reads the MailMessage from the given input stream. /// @@ -212,7 +248,7 @@ public: void write(std::ostream& ostr) const; /// Writes the mail message to the given output stream. - + static std::string encodeWord(const std::string& text, const std::string& charset = "UTF-8"); /// If the given string contains non-ASCII characters, /// encodes the given string using RFC 2047 "Q" word encoding. @@ -223,15 +259,25 @@ public: /// Returns the encoded string, or the original string if it /// consists only of ASCII characters. + static const std::string HEADER_SUBJECT; + static const std::string HEADER_FROM; + static const std::string HEADER_TO; + static const std::string HEADER_CC; + static const std::string HEADER_BCC; + static const std::string HEADER_DATE; + static const std::string HEADER_CONTENT_TYPE; + static const std::string HEADER_CONTENT_TRANSFER_ENCODING; + static const std::string HEADER_CONTENT_DISPOSITION; + static const std::string HEADER_CONTENT_ID; + static const std::string HEADER_MIME_VERSION; + static const std::string EMPTY_HEADER; + static const std::string TEXT_PLAIN; + static const std::string CTE_7BIT; + static const std::string CTE_8BIT; + static const std::string CTE_QUOTED_PRINTABLE; + static const std::string CTE_BASE64; + protected: - struct Part - { - std::string name; - PartSource* pSource; - ContentDisposition disposition; - ContentTransferEncoding encoding; - }; - typedef std::vector PartVec; void makeMultipart(); void writeHeader(const MessageHeader& header, std::ostream& ostr) const; @@ -247,23 +293,6 @@ protected: static int lineLength(const std::string& str); static void appendRecipient(const MailRecipient& recipient, std::string& str); - static const std::string HEADER_SUBJECT; - static const std::string HEADER_FROM; - static const std::string HEADER_TO; - static const std::string HEADER_CC; - static const std::string HEADER_BCC; - static const std::string HEADER_DATE; - static const std::string HEADER_CONTENT_TYPE; - static const std::string HEADER_CONTENT_TRANSFER_ENCODING; - static const std::string HEADER_CONTENT_DISPOSITION; - static const std::string HEADER_MIME_VERSION; - static const std::string EMPTY_HEADER; - static const std::string TEXT_PLAIN; - static const std::string CTE_7BIT; - static const std::string CTE_8BIT; - static const std::string CTE_QUOTED_PRINTABLE; - static const std::string CTE_BASE64; - private: MailMessage(const MailMessage&); MailMessage& operator = (const MailMessage&); @@ -272,6 +301,8 @@ private: PartVec _parts; std::string _content; ContentTransferEncoding _encoding; + mutable std::string _boundary; + PartStoreFactory* _pStoreFactory; }; @@ -290,6 +321,12 @@ inline const std::string& MailMessage::getContent() const } +inline const MailMessage::PartVec& MailMessage::parts() const +{ + return _parts; +} + + } } // namespace Poco::Net diff --git a/Net/include/Poco/Net/PartSource.h b/Net/include/Poco/Net/PartSource.h index 39b1c2085..acb4bfdc8 100644 --- a/Net/include/Poco/Net/PartSource.h +++ b/Net/include/Poco/Net/PartSource.h @@ -60,7 +60,7 @@ public: /// /// Subclasses must override this method. - virtual const std::string& filename(); + virtual const std::string& filename() const; /// Returns the filename for the part or attachment. /// /// May be overridded by subclasses. The default diff --git a/Net/include/Poco/Net/PartStore.h b/Net/include/Poco/Net/PartStore.h new file mode 100644 index 000000000..fb776fdd4 --- /dev/null +++ b/Net/include/Poco/Net/PartStore.h @@ -0,0 +1,128 @@ +// +// PartStore.h +// +// $Id: //poco/1.4/Net/include/Poco/Net/PartStore.h#1 $ +// +// Library: Net +// Package: Messages +// Module: PartStore +// +// Definition of the PartStore class. +// +// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + + +#ifndef Net_PartStore_INCLUDED +#define Net_PartStore_INCLUDED + + +#include "Poco/Net/Net.h" +#include "Poco/Net/PartSource.h" +#include "Poco/FileStream.h" + + +namespace Poco { +namespace Net { + + +class Net_API PartStore: public PartSource + /// A parent class for part stores storing message parts. +{ +public: + PartStore(const std::string& mediaType); + /// Creates the PartStore for the given MIME type. + + ~PartStore(); + /// Destroys the PartFileStore. + +private: + PartStore(); +}; + + +class Net_API FilePartStore: public PartStore + /// An implementation of PartSource for persisting + /// parts (usually email attachment files) to the file system. +{ +public: + FilePartStore(const std::string& content, const std::string& mediaType, const std::string& filename = ""); + /// Creates the FilePartStore for the given MIME type. + /// For security purposes, attachment filename is NOT used to save file to the file system. + /// A unique temporary file name is used to persist the file. + /// The given filename parameter is the message part (attachment) filename (see filename()) only. + /// + /// Throws an exception if the file cannot be opened. + + ~FilePartStore(); + /// Destroys the FilePartStore. + + std::istream& stream(); + /// Returns a file input stream for the given file. + + const std::string& filename() const; + /// Returns the filename portion of the path. + /// This is the name under which the file is known + /// to the user of this class (typically, MailMessage + /// class). The real name of the file as saved + /// to the filesystem can be obtained by calling + /// path() member function. + + const std::string& path() const; + /// Returns the full path to the file as saved + /// to the file system. For security reasons, + /// file is not saved under the real file name + /// (as specified by the user). + +private: + std::string _filename; + std::string _path; + Poco::FileStream _fstr; +}; + + +class PartStoreFactory + /// Parent factory class for part stores creation. +{ +public: + virtual PartSource* createPartStore(const std::string& content, const std::string& mediaType, const std::string& filename = "") = 0; +}; + + +class FilePartStoreFactory: public PartStoreFactory +{ +public: + PartSource* createPartStore(const std::string& content, const std::string& mediaType, const std::string& filename = "") + { + return new FilePartStore(content, mediaType, filename); + } +}; + + +} } // namespace Poco::Net + + +#endif // Net_PartStore_INCLUDED diff --git a/Net/include/Poco/Net/StringPartSource.h b/Net/include/Poco/Net/StringPartSource.h index 9b77953eb..92bbaac22 100644 --- a/Net/include/Poco/Net/StringPartSource.h +++ b/Net/include/Poco/Net/StringPartSource.h @@ -72,7 +72,7 @@ public: std::istream& stream(); /// Returns a string input stream for the string. - const std::string& filename(); + const std::string& filename() const; /// Returns the filename portion of the path. private: diff --git a/Net/src/MailMessage.cpp b/Net/src/MailMessage.cpp index e60bc00d8..88ba24704 100644 --- a/Net/src/MailMessage.cpp +++ b/Net/src/MailMessage.cpp @@ -40,8 +40,10 @@ #include "Poco/Net/MultipartWriter.h" #include "Poco/Net/PartSource.h" #include "Poco/Net/PartHandler.h" +#include "Poco/Net/StringPartSource.h" #include "Poco/Net/QuotedPrintableEncoder.h" #include "Poco/Net/QuotedPrintableDecoder.h" +#include "Poco/Net/NameValueCollection.h" #include "Poco/Base64Encoder.h" #include "Poco/Base64Decoder.h" #include "Poco/StreamCopier.h" @@ -49,6 +51,7 @@ #include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeParser.h" #include "Poco/String.h" +#include "Poco/StringTokenizer.h" #include "Poco/StreamCopier.h" #include "Poco/NumberFormatter.h" #include @@ -60,6 +63,7 @@ using Poco::StreamCopier; using Poco::DateTimeFormat; using Poco::DateTimeFormatter; using Poco::DateTimeParser; +using Poco::StringTokenizer; using Poco::icompare; @@ -69,21 +73,107 @@ namespace Net { namespace { - class StringPartHandler: public PartHandler + class MultiPartHandler: public PartHandler + /// This is a default part handler for multipart messages, used when there + /// is no external handler provided to he MailMessage. This handler + /// will handle all types of message parts, including attachments. { public: - StringPartHandler(std::string& content): - _str(content) + MultiPartHandler(MailMessage* pMsg): _pMsg(pMsg) + /// Creates multi part handler. + /// The pMsg pointer points to the calling MailMessage + /// and will be used to properly populate it, so the + /// message content could be written out unmodified + /// in its entirety, including attachments. { } - ~StringPartHandler() + ~MultiPartHandler() + /// Destroys string part handler. { } void handlePart(const MessageHeader& header, std::istream& stream) + /// Handles a part. If message pointer was provided at construction time, + /// the message pointed to will be properly populated so it could be written + /// back out at a later point in time. { - Poco::StreamCopier::copyToString(stream, _str); + std::string tmp; + Poco::StreamCopier::copyToString(stream, tmp); + if (_pMsg) + { + + MailMessage::ContentTransferEncoding cte = MailMessage::ENCODING_7BIT; + std::string enc = header[MailMessage::HEADER_CONTENT_TRANSFER_ENCODING]; + if (enc == MailMessage::CTE_8BIT) + cte = MailMessage::ENCODING_8BIT; + else if (enc == MailMessage::CTE_QUOTED_PRINTABLE) + cte = MailMessage::ENCODING_QUOTED_PRINTABLE; + else if (enc == MailMessage::CTE_BASE64) + cte = MailMessage::ENCODING_BASE64; + + NameValueCollection::ConstIterator it = header.begin(); + NameValueCollection::ConstIterator end = header.end(); + PartSource* pPS = _pMsg->getPartStore(tmp, + header[MailMessage::HEADER_CONTENT_TYPE], + getFileNameFromDisp(it->second)); + poco_check_ptr (pPS); + for (; it != end; ++it) + { + if (MailMessage::HEADER_CONTENT_DISPOSITION == it->first) + { + if (it->second == "inline") _pMsg->addContent(pPS, cte); + else _pMsg->addAttachment("", pPS, cte); + } + + pPS->headers().set(it->first, it->second); + } + } + } + + private: + std::string getFileNameFromDisp(const std::string& str) + { + StringTokenizer st(str, ";=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + StringTokenizer::Iterator it = st.begin(); + StringTokenizer::Iterator end = st.end(); + for (; it != end; ++it) { if (*it == "filename") break; } + if (it != end) + { + ++it; + if (it == end) return ""; + return *it; + } + return ""; + } + + MailMessage* _pMsg; + }; + + + class StringPartHandler: public PartHandler + /// This is a default part handler, used when there is no + /// external handler provided to the MailMessage. This handler + /// handles only single-part messages. + { + public: + StringPartHandler(std::string& content): _str(content) + /// Creates string part handler. + /// The content parameter represents the part content. + { + } + + ~StringPartHandler() + /// Destroys string part handler. + { + } + + void handlePart(const MessageHeader& header, std::istream& stream) + /// Handles a part. + { + std::string tmp; + Poco::StreamCopier::copyToString(stream, tmp); + _str.append(tmp); } private: @@ -101,6 +191,7 @@ const std::string MailMessage::HEADER_DATE("Date"); const std::string MailMessage::HEADER_CONTENT_TYPE("Content-Type"); const std::string MailMessage::HEADER_CONTENT_TRANSFER_ENCODING("Content-Transfer-Encoding"); const std::string MailMessage::HEADER_CONTENT_DISPOSITION("Content-Disposition"); +const std::string MailMessage::HEADER_CONTENT_ID("Content-ID"); const std::string MailMessage::HEADER_MIME_VERSION("Mime-Version"); const std::string MailMessage::EMPTY_HEADER; const std::string MailMessage::TEXT_PLAIN("text/plain"); @@ -110,7 +201,8 @@ const std::string MailMessage::CTE_QUOTED_PRINTABLE("quoted-printable"); const std::string MailMessage::CTE_BASE64("base64"); -MailMessage::MailMessage() +MailMessage::MailMessage(PartStoreFactory* pStoreFactory): + _pStoreFactory(pStoreFactory) { Poco::Timestamp now; setDate(now); @@ -263,8 +355,16 @@ void MailMessage::read(std::istream& istr, PartHandler& handler) void MailMessage::read(std::istream& istr) { readHeader(istr); - StringPartHandler handler(_content); - readPart(istr, *this, handler); + if (isMultipart()) + { + MultiPartHandler handler(this); + readMultipart(istr, handler); + } + else + { + StringPartHandler handler(_content); + readPart(istr, *this, handler); + } } @@ -304,14 +404,14 @@ void MailMessage::writeHeader(const MessageHeader& header, std::ostream& ostr) c void MailMessage::writeMultipart(MessageHeader& header, std::ostream& ostr) const { - std::string boundary(MultipartWriter::createBoundary()); + if (_boundary.empty()) _boundary = MultipartWriter::createBoundary(); MediaType mediaType(getContentType()); - mediaType.setParameter("boundary", boundary); + mediaType.setParameter("boundary", _boundary); header.set(HEADER_CONTENT_TYPE, mediaType.toString()); header.set(HEADER_MIME_VERSION, "1.0"); writeHeader(header, ostr); - MultipartWriter writer(ostr, boundary); + MultipartWriter writer(ostr, _boundary); for (PartVec::const_iterator it = _parts.begin(); it != _parts.end(); ++it) { writePart(writer, *it); @@ -384,8 +484,8 @@ void MailMessage::readHeader(std::istream& istr) void MailMessage::readMultipart(std::istream& istr, PartHandler& handler) { MediaType contentType(getContentType()); - std::string boundary = contentType.getParameter("boundary"); - MultipartReader reader(istr, boundary); + _boundary = contentType.getParameter("boundary"); + MultipartReader reader(istr, _boundary); while (reader.hasNextPart()) { MessageHeader partHeader; @@ -580,4 +680,11 @@ std::string MailMessage::encodeWord(const std::string& text, const std::string& } +PartSource* MailMessage::getPartStore(const std::string& content, const std::string& mediaType, const std::string& filename) +{ + if (!_pStoreFactory) return new StringPartSource(content, mediaType, filename); + else return _pStoreFactory->createPartStore(content, mediaType, filename); +} + + } } // namespace Poco::Net diff --git a/Net/src/PartSource.cpp b/Net/src/PartSource.cpp index a09f6ebf1..1bb1c1be2 100644 --- a/Net/src/PartSource.cpp +++ b/Net/src/PartSource.cpp @@ -64,7 +64,7 @@ namespace } -const std::string& PartSource::filename() +const std::string& PartSource::filename() const { return EMPTY; } diff --git a/Net/src/PartStore.cpp b/Net/src/PartStore.cpp new file mode 100644 index 000000000..f144ec45d --- /dev/null +++ b/Net/src/PartStore.cpp @@ -0,0 +1,103 @@ +// +// PartStore.cpp +// +// $Id: //poco/1.4/Net/src/PartStore.cpp#1 $ +// +// Library: Net +// Package: Messages +// Module: PartStore +// +// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + + +#include "Poco/Net/PartStore.h" +#include "Poco/TemporaryFile.h" +#include "Poco/File.h" +#include "Poco/Exception.h" + + +namespace Poco { +namespace Net { + + +/// PartStore + +PartStore::PartStore(const std::string& mediaType): PartSource(mediaType) +{ +} + + +PartStore::~PartStore() +{ +} + + +/// FilePartStore + +FilePartStore::FilePartStore(const std::string& content, const std::string& mediaType, const std::string& filename): + PartStore(mediaType), + _filename(filename), + _path(TemporaryFile::tempName()), + _fstr(_path) +{ + _fstr << content << std::flush; + _fstr.seekg(0, std::ios::beg); +} + + +FilePartStore::~FilePartStore() +{ + try + { + _fstr.close(); + File(_path).remove(); + } + catch (Exception&) + { + } +} + + +std::istream& FilePartStore::stream() +{ + return _fstr; +} + + +const std::string& FilePartStore::filename() const +{ + return _filename; +} + + +const std::string& FilePartStore::path() const +{ + return _path; +} + + +} } // namespace Poco::Net diff --git a/Net/src/StringPartSource.cpp b/Net/src/StringPartSource.cpp index b2eb4260a..eb391ab81 100644 --- a/Net/src/StringPartSource.cpp +++ b/Net/src/StringPartSource.cpp @@ -74,7 +74,7 @@ std::istream& StringPartSource::stream() } -const std::string& StringPartSource::filename() +const std::string& StringPartSource::filename() const { return _filename; } diff --git a/Net/testsuite/src/MailMessageTest.cpp b/Net/testsuite/src/MailMessageTest.cpp index cfe137c1c..d58fcce74 100644 --- a/Net/testsuite/src/MailMessageTest.cpp +++ b/Net/testsuite/src/MailMessageTest.cpp @@ -37,8 +37,11 @@ #include "Poco/Net/MailRecipient.h" #include "Poco/Net/PartHandler.h" #include "Poco/Net/StringPartSource.h" +#include "Poco/Net/PartStore.h" #include "Poco/Net/MediaType.h" #include "Poco/Timestamp.h" +#include "Poco/FileStream.h" +#include "Poco/String.h" #include #include @@ -49,7 +52,12 @@ using Poco::Net::MessageHeader; using Poco::Net::PartHandler; using Poco::Net::MediaType; using Poco::Net::StringPartSource; +using Poco::Net::FilePartStoreFactory; +using Poco::Net::FilePartStore; using Poco::Timestamp; +using Poco::FileInputStream; +using Poco::replaceInPlace; +using Poco::icompare; namespace @@ -135,6 +143,7 @@ void MailMessageTest::testWriteQP() std::ostringstream str; message.write(str); std::string s = str.str(); + assert (s == "Date: Thu, 1 Jan 1970 00:00:00 GMT\r\n" "Content-Type: text/plain\r\n" @@ -302,9 +311,9 @@ void MailMessageTest::testWriteMultiPart() "VGhpcyBpcyBzb21lIGJpbmFyeSBkYXRhLiBSZWFsbHku\r\n" "--$--\r\n" ); - std::string::size_type p2 = s.rfind("--"); - std::string::size_type p1 = s.rfind("--", p2 - 1); - std::string boundary(s, p1 + 2, p2 - 2 - p1); + std::string::size_type p1 = s.find('=') + 1; + std::string::size_type p2 = s.find('\r', p1); + std::string boundary(s, p1, p2 - p1); std::string msg; for (std::string::const_iterator it = rawMsg.begin(); it != rawMsg.end(); ++it) { @@ -313,6 +322,7 @@ void MailMessageTest::testWriteMultiPart() else msg += *it; } + assert (s == msg); } @@ -416,6 +426,111 @@ void MailMessageTest::testReadMultiPart() } +void MailMessageTest::testReadWriteMultiPart() +{ + std::string msgin( + "Content-Type: multipart/mixed; boundary=MIME_boundary_31E8A8D61DF53389\r\n" + "Date: Thu, 1 Jan 1970 00:00:00 GMT\r\n" + "From: poco@appinf.com\r\n" + "Mime-Version: 1.0\r\n" + "Subject: Test Message\r\n" + "To: John Doe \r\n" + "\r\n" + "--MIME_boundary_31E8A8D61DF53389\r\n" + "Content-Disposition: inline\r\n" + "Content-Transfer-Encoding: 8bit\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "Hello World!\r\n" + "\r\n" + "--MIME_boundary_31E8A8D61DF53389\r\n" + "Content-Disposition: attachment; filename=sample.dat\r\n" + "Content-ID: abcd1234\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Type: application/octet-stream; name=sample\r\n" + "\r\n" + "VGhpcyBpcyBzb21lIGJpbmFyeSBkYXRhLiBSZWFsbHku\r\n" + "--MIME_boundary_31E8A8D61DF53389--\r\n" + ); + + std::istringstream istr(msgin); + std::ostringstream ostr; + MailMessage message; + + message.read(istr); + message.write(ostr); + + std::string msgout(ostr.str()); + assert (msgout == msgin); +} + + +void MailMessageTest::testReadWriteMultiPartStore() +{ + std::string msgin( + "Content-Type: multipart/mixed; boundary=MIME_boundary_31E8A8D61DF53389\r\n" + "Date: Thu, 1 Jan 1970 00:00:00 GMT\r\n" + "From: poco@appinf.com\r\n" + "Mime-Version: 1.0\r\n" + "Subject: Test Message\r\n" + "To: John Doe \r\n" + "\r\n" + "--MIME_boundary_31E8A8D61DF53389\r\n" + "Content-Disposition: inline\r\n" + "Content-Transfer-Encoding: 8bit\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "Hello World!\r\n" + "\r\n" + "--MIME_boundary_31E8A8D61DF53389\r\n" + "Content-Disposition: attachment; filename=sample.dat\r\n" + "Content-ID: abcd1234\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Type: application/octet-stream; name=sample\r\n" + "\r\n" + "VGhpcyBpcyBzb21lIGJpbmFyeSBkYXRhLiBSZWFsbHku\r\n" + "--MIME_boundary_31E8A8D61DF53389--\r\n" + ); + + std::istringstream istr(msgin); + std::ostringstream ostr; + FilePartStoreFactory pfsf; + MailMessage message(&pfsf); + + message.read(istr); + + MailMessage::PartVec::const_iterator it = message.parts().begin(); + MailMessage::PartVec::const_iterator end = message.parts().end(); + for (; it != end; ++it) + { + FilePartStore* fps = dynamic_cast(it->pSource); + if (fps && fps->filename().size()) + { + std::string filename = fps->filename(); + assert (filename == "sample.dat"); + std::string path = fps->path(); + // for security reasons, the filesystem temporary + // filename is not the same as attachment name + std::size_t sz = (path.size() > filename.size()) ? filename.size() : path.size(); + assert (0 != icompare(path, path.size() - sz, sz, path)); + + Poco::FileInputStream fis(path); + assert (fis.good()); + std::string read; + std::string line; + while (std::getline(fis, line)) read += line; + + assert (!read.empty()); + assert (read == "This is some binary data. Really."); + } + } + + message.write(ostr); + std::string msgout(ostr.str()); + assert (msgout == msgin); +} + + void MailMessageTest::testEncodeWord() { std::string plain("this is pure ASCII"); @@ -460,6 +575,8 @@ CppUnit::Test* MailMessageTest::suite() CppUnit_addTest(pSuite, MailMessageTest, testReadQP); CppUnit_addTest(pSuite, MailMessageTest, testRead8Bit); CppUnit_addTest(pSuite, MailMessageTest, testReadMultiPart); + CppUnit_addTest(pSuite, MailMessageTest, testReadWriteMultiPart); + CppUnit_addTest(pSuite, MailMessageTest, testReadWriteMultiPartStore); CppUnit_addTest(pSuite, MailMessageTest, testEncodeWord); return pSuite; diff --git a/Net/testsuite/src/MailMessageTest.h b/Net/testsuite/src/MailMessageTest.h index 4843cdfb7..2a9c3f437 100644 --- a/Net/testsuite/src/MailMessageTest.h +++ b/Net/testsuite/src/MailMessageTest.h @@ -51,6 +51,8 @@ public: void testWriteBase64(); void testWriteManyRecipients(); void testWriteMultiPart(); + void testReadWriteMultiPart(); + void testReadWriteMultiPartStore(); void testReadQP(); void testRead8Bit(); void testReadMultiPart();