diff --git a/SMTPEmail.pro b/SMTPEmail.pro index ed9b09b..10e70a2 100644 --- a/SMTPEmail.pro +++ b/SMTPEmail.pro @@ -19,7 +19,11 @@ SOURCES += \ src/mimemessage.cpp \ src/mimepart.cpp \ src/mimetext.cpp \ - src/smtpclient.cpp + src/smtpclient.cpp \ + src/quotedprintable.cpp \ + src/mimemultipart.cpp \ + test.cpp \ + src/mimecontentformatter.cpp HEADERS += \ src/emailaddress.h \ @@ -31,7 +35,10 @@ HEADERS += \ src/mimepart.h \ src/mimetext.h \ src/smtpclient.h \ - src/SmtpMime + src/SmtpMime \ + src/quotedprintable.h \ + src/mimemultipart.h \ + src/mimecontentformatter.h OTHER_FILES += \ LICENSE \ diff --git a/src/mimecontentformatter.cpp b/src/mimecontentformatter.cpp new file mode 100644 index 0000000..2beebcb --- /dev/null +++ b/src/mimecontentformatter.cpp @@ -0,0 +1,44 @@ +#include "mimecontentformatter.h" + +MimeContentFormatter::MimeContentFormatter(int max_length) : + max_length(max_length) +{} + +QString MimeContentFormatter::format(const QString &content, bool quotedPrintable) { + + QString out; + + int chars = 0; + for (int i = 0; i < content.length() ; ++i) { + chars++; + if (!quotedPrintable) { + if (chars > max_length) { + out.append("\r\n"); + chars = 1; + } + } + else { + if (content[i] == '\n') { // new line + out.append(content[i]); + chars = 0; + continue; + } + + if ((chars > max_length - 1) + || ((content[i] == '=') && (chars > max_length - 3) )) { + out.append('='); + out.append("\r\n"); + chars = 1; + } + + } + out.append(content[i]); + } + + return out; + +} + +void MimeContentFormatter::setMaxLength(int l) { + max_length = l; +} diff --git a/src/mimecontentformatter.h b/src/mimecontentformatter.h new file mode 100644 index 0000000..1f539fc --- /dev/null +++ b/src/mimecontentformatter.h @@ -0,0 +1,22 @@ +#ifndef MIMECONTENTFORMATTER_H +#define MIMECONTENTFORMATTER_H + +#include +#include + +class MimeContentFormatter : public QObject +{ + Q_OBJECT +public: + MimeContentFormatter (int max_length = 76); + + void setMaxLength(int l); + + QString format(const QString &content, bool quotedPrintable = false); + +protected: + int max_length; + +}; + +#endif // MIMECONTENTFORMATTER_H diff --git a/src/mimemessage.cpp b/src/mimemessage.cpp index f7a1a54..f947c85 100644 --- a/src/mimemessage.cpp +++ b/src/mimemessage.cpp @@ -18,12 +18,15 @@ #include #include "quotedprintable.h" +#include /* [1] Constructors and Destructors */ -MimeMessage::MimeMessage() : +MimeMessage::MimeMessage(bool createAutoMimeContent) : hEncoding(MimePart::_8Bit) { + if (createAutoMimeContent) + this->content = new MimeMultiPart(); } MimeMessage::~MimeMessage() @@ -41,9 +44,32 @@ void MimeMessage::setSender(EmailAddress* e) this->sender = e; } -void MimeMessage::addRecipient(EmailAddress* rcpt) +void MimeMessage::addRecipient(EmailAddress* rcpt, RecipientType type) { - this->recipients << rcpt; + switch (type) + { + case To: + recipientsTo << rcpt; + break; + case Cc: + recipientsCc << rcpt; + break; + case Bcc: + recipientsBcc << rcpt; + break; + } +} + +void MimeMessage::addTo(EmailAddress* rcpt) { + this->recipientsTo << rcpt; +} + +void MimeMessage::addCc(EmailAddress* rcpt) { + this->recipientsCc << rcpt; +} + +void MimeMessage::addBcc(EmailAddress* rcpt) { + this->recipientsBcc << rcpt; } void MimeMessage::setSubject(const QString & subject) @@ -53,7 +79,9 @@ void MimeMessage::setSubject(const QString & subject) void MimeMessage::addPart(MimePart *part) { - this->parts << part; + if (typeid(*content) == typeid(MimeMultiPart)) { + ((MimeMultiPart*) content)->addPart(part); + }; } void MimeMessage::setHeaderEncoding(MimePart::Encoding hEnc) @@ -66,9 +94,18 @@ const EmailAddress & MimeMessage::getSender() const return *sender; } -const QList & MimeMessage::getRecipients() const +const QList & MimeMessage::getRecipients(RecipientType type) const { - return recipients; + switch (type) + { + default: + case To: + return recipientsTo; + case Cc: + return recipientsCc; + case Bcc: + return recipientsBcc; + } } const QString & MimeMessage::getSubject() const @@ -78,7 +115,15 @@ const QString & MimeMessage::getSubject() const const QList & MimeMessage::getParts() const { - return parts; + if (typeid(content) == typeid(MimeMultiPart)) { + return static_cast(content)->getParts(); + } + else { + QList *res = new QList(); + res->append(content); + return *res; + } + } /* [2] --- */ @@ -112,11 +157,12 @@ QString MimeMessage::toString() /* ---------------------------------- */ - /* ------- Recipients / To ---------- */ - QList::iterator it; - for (it = recipients.begin(); it != recipients.end(); ++it) + /* ------- Recipients / To ---------- */ + mime += "To:"; + QList::iterator it; int i; + for (i = 0, it = recipientsTo.begin(); it != recipientsTo.end(); ++it, ++i) { - mime += "To:"; + if (i != 0) { mime += ","; } if ((*it)->getName() != "") { @@ -132,13 +178,40 @@ QString MimeMessage::toString() mime += " " + (*it)->getName(); } } - mime += " <" + (*it)->getAddress() + ">\r\n"; + mime += " <" + (*it)->getAddress() + ">"; } + mime += "\r\n"; + /* ---------------------------------- */ + + /* ------- Recipients / Cc ---------- */ + mime += "Cc:"; + for (i = 0, it = recipientsCc.begin(); it != recipientsCc.end(); ++it, ++i) + { + if (i != 0) { mime += ","; } + + if ((*it)->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append((*it)->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append((*it)->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + (*it)->getName(); + } + } + mime += " <" + (*it)->getAddress() + ">"; + } + mime += "\r\n"; /* ---------------------------------- */ /* ------------ Subject ------------- */ mime += "Subject: "; + switch (hEncoding) { case MimePart::Base64: @@ -153,26 +226,9 @@ QString MimeMessage::toString() /* ---------------------------------- */ mime += "\r\n"; - - QString boundary = "----MIME-part-boundary=" + QByteArray().append(QDateTime::currentDateTime().toString()).toBase64() + "-end"; - mime += "MIME-Version: 1.0\r\n"; - mime += "Content-type: multipart/mixed; boundary=\"" + boundary + "\"\r\n\r\n"; - - /* ====== END OF MIME HEADER ======== */ - - boundary = "--" + boundary; - - /* ========== MIME BODY ============= */ - - QList::iterator i; - for (i = parts.begin(); i != parts.end(); ++i) - mime += boundary + "\r\n" + (*i)->toString(); - - mime += boundary + "--\r\n"; - - /* ====== END OF MIME BODY ========= */ + mime += content->toString(); return mime; } diff --git a/src/mimemessage.h b/src/mimemessage.h index 8a82ceb..24f1508 100644 --- a/src/mimemessage.h +++ b/src/mimemessage.h @@ -18,6 +18,7 @@ #define MIMEMESSAGE_H #include "mimepart.h" +#include "mimemultipart.h" #include "emailaddress.h" #include @@ -25,9 +26,15 @@ class MimeMessage : public QObject { public: + enum RecipientType { + To, // primary + Cc, // carbon copy + Bcc // blind carbon copy + }; + /* [1] Constructors and Destructors */ - MimeMessage(); + MimeMessage(bool createAutoMimeConent = true); ~MimeMessage(); /* [1] --- */ @@ -36,14 +43,17 @@ public: /* [2] Getters and Setters */ void setSender(EmailAddress* e); - void addRecipient(EmailAddress* rcpt); + void addRecipient(EmailAddress* rcpt, RecipientType type = To); + void addTo(EmailAddress* rcpt); + void addCc(EmailAddress* rcpt); + void addBcc(EmailAddress* rcpt); void setSubject(const QString & subject); void addPart(MimePart* part); void setHeaderEncoding(MimePart::Encoding); const EmailAddress & getSender() const; - const QList & getRecipients() const; + const QList & getRecipients(RecipientType type = To) const; const QString & getSubject() const; const QList & getParts() const; @@ -61,9 +71,9 @@ protected: /* [4] Protected members */ EmailAddress* sender; - QList recipients; + QList recipientsTo, recipientsCc, recipientsBcc; QString subject; - QList parts; + MimePart *content; MimePart::Encoding hEncoding; diff --git a/src/mimemultipart.cpp b/src/mimemultipart.cpp new file mode 100644 index 0000000..9f0613f --- /dev/null +++ b/src/mimemultipart.cpp @@ -0,0 +1,52 @@ +#include "mimemultipart.h" +#include +#include + +const QString MULTI_PART_NAMES[] = { + "multipart/mixed", // Mixed + "multipart/digest", // Digest + "multipart/alternative", // Alternative + "multipart/related", // Related + "multipart/report", // Report + "multipart/signed", // Signed + "multipart/encrypted" // Encrypted +}; + +MimeMultiPart::MimeMultiPart(MultiPartType type) +{ + this->type = type; + this->cType = MULTI_PART_NAMES[this->type]; + this->cEncoding = _8Bit; + + qsrand(QTime::currentTime().msec()); + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(QByteArray().append(qrand())); + cBoundary = md5.result().toHex(); +} + +MimeMultiPart::~MimeMultiPart() { + +} + +void MimeMultiPart::addPart(MimePart *part) { + parts.append(part); +} + +const QList & MimeMultiPart::getParts() const { + return parts; +} + +void MimeMultiPart::prepare() { + QList::iterator it; + + content = ""; + for (it = parts.begin(); it != parts.end(); it++) { + content += "--" + cBoundary + "\r\n"; + (*it)->prepare(); + content += (*it)->toString(); + }; + + content += "--" + cBoundary + "--\r\n"; + + MimePart::prepare(); +} diff --git a/src/mimemultipart.h b/src/mimemultipart.h new file mode 100644 index 0000000..1bf977c --- /dev/null +++ b/src/mimemultipart.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2012 - Tőkés Attila (tokes_atti@yahoo.com) + + This file is part of SmtpClient for Qt. + + SmtpClient for Qt is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + SmtpClient for Qt is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEMULTIPART_H +#define MIMEMULTIPART_H + +#include "mimepart.h" + +class MimeMultiPart : public MimePart +{ + Q_OBJECT +public: + + /* [0] Enums */ + enum MultiPartType { + Mixed = 0, // RFC 2046, section 5.1.3 + Digest = 1, // RFC 2046, section 5.1.5 + Alternative = 2, // RFC 2046, section 5.1.4 + Related = 3, // RFC 2387 + Report = 4, // RFC 6522 + Signed = 5, // RFC 1847, section 2.1 + Encrypted = 6 // RFC 1847, section 2.2 + }; + + /* [0] --- */ + + /* [1] Constructors and Destructors */ + MimeMultiPart(const MultiPartType type = Related); + + ~MimeMultiPart(); + + /* [1] --- */ + + /* [2] Getters and Setters */ + + const QList & getParts() const; + + /* [2] --- */ + + /* [3] Public methods */ + + void addPart(MimePart *part); + + virtual void prepare(); + + /* [3] --- */ + +protected: + QList< MimePart* > parts; + + MultiPartType type; + + +}; + +#endif // MIMEMULTIPART_H diff --git a/src/mimepart.cpp b/src/mimepart.cpp index d87064b..9354def 100644 --- a/src/mimepart.cpp +++ b/src/mimepart.cpp @@ -23,6 +23,7 @@ MimePart::MimePart() { cEncoding = _7Bit; prepared = false; + cBoundary = ""; } MimePart::~MimePart() @@ -47,7 +48,7 @@ void MimePart::setHeader(const QString & header) void MimePart::addHeaderLine(const QString & line) { - this->header += line; + this->header += line + "\r\n"; } const QString& MimePart::getHeader() const @@ -143,6 +144,9 @@ void MimePart::prepare() if (cCharset != "") mimeString.append("; charset=").append(cCharset); + if (cBoundary != "") + mimeString.append("; boundary=").append(cBoundary); + mimeString.append("\r\n"); /* ------------ */ @@ -170,14 +174,15 @@ void MimePart::prepare() mimeString.append("Content-ID: <").append(cId).append(">\r\n"); /* ---------- */ - - /* ------------------------- */ + /* Addition header lines */ mimeString.append(header).append("\r\n"); + /* ------------------------- */ + /* === End of Header Prepare === */ - /* === Content Encoding === */ + /* === Content === */ switch (cEncoding) { case _7Bit: @@ -187,14 +192,14 @@ void MimePart::prepare() mimeString.append(content); break; case Base64: - mimeString.append(content.toBase64()); + mimeString.append(formatter.format(content.toBase64())); break; case QuotedPrintable: - mimeString.append(QuotedPrintable::encode(content)); + mimeString.append(formatter.format(QuotedPrintable::encode(content), true)); break; } mimeString.append("\r\n"); - /* === End of Content Encoding === */ + /* === End of Content === */ prepared = true; } diff --git a/src/mimepart.h b/src/mimepart.h index 3b07644..6ef60d3 100644 --- a/src/mimepart.h +++ b/src/mimepart.h @@ -18,6 +18,7 @@ #define MIMEPART_H #include +#include "mimecontentformatter.h" class MimePart : public QObject { @@ -93,11 +94,14 @@ protected: QString cName; QString cType; QString cCharset; + QString cBoundary; Encoding cEncoding; QString mimeString; bool prepared; + MimeContentFormatter formatter; + /* [4] --- */ }; diff --git a/src/smtpclient.cpp b/src/smtpclient.cpp index 80489c3..b160af2 100644 --- a/src/smtpclient.cpp +++ b/src/smtpclient.cpp @@ -267,9 +267,32 @@ bool SmtpClient::sendMail(MimeMessage& email) if (responseCode != 250) return false; // Send RCPT command for each recipient - for (int i = 0; i < email.getRecipients().size(); ++i) + QList::const_iterator it, itEnd; + // To (primary recipients) + for (it = email.getRecipients().begin(), itEnd = email.getRecipients().end(); + it != itEnd; ++it) { - sendMessage("RCPT TO: <" + email.getRecipients().at(i)->getAddress() + ">"); + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Cc (carbon copy) + for (it = email.getRecipients(MimeMessage::Cc).begin(), itEnd = email.getRecipients(MimeMessage::Cc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Bcc (blind carbon copy) + for (it = email.getRecipients(MimeMessage::Bcc).begin(), itEnd = email.getRecipients(MimeMessage::Bcc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); waitForResponse(); if (responseCode != 250) return false;