QxtGlobalShortcut/src/network/qxtmailmessage.cpp
2013-08-22 14:18:44 -05:00

915 lines
28 KiB
C++

/****************************************************************************
** Copyright (c) 2006 - 2011, the LibQxt project.
** See the Qxt AUTHORS file for a list of authors and copyright holders.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the LibQxt project nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
** <http://libqxt.org> <foundation@libqxt.org>
*****************************************************************************/
/*!
* \class QxtMailMessage
* \inmodule QxtNetwork
* \brief The QxtMailMessage class encapsulates an e-mail according to RFC 2822 and related specifications
* TODO: {implicitshared}
*/
#include "qxtmailmessage.h"
#include "qxtmail_p.h"
#include <QTextCodec>
#include <QUuid>
#include <QDir>
#include <QtDebug>
#include <QRegExp>
struct QxtMailMessagePrivate : public QSharedData
{
QxtMailMessagePrivate() {}
QxtMailMessagePrivate(const QxtMailMessagePrivate& other)
: QSharedData(other), rcptTo(other.rcptTo), rcptCc(other.rcptCc), rcptBcc(other.rcptBcc),
subject(other.subject), body(other.body), sender(other.sender),
extraHeaders(other.extraHeaders), attachments(other.attachments) {}
QStringList rcptTo, rcptCc, rcptBcc;
QString subject, body, sender;
QHash<QString, QString> extraHeaders;
QHash<QString, QxtMailAttachment> attachments;
mutable QByteArray boundary;
};
class QxtRfc2822Parser
{
public:
QxtMailMessagePrivate* parse(const QByteArray& buffer);
private:
enum State {
Headers,
Body
};
State state;
QString currentHeaderKey;
QStringList currentHeaderValue;
void parseHeader(const QByteArray& line, QHash<QString, QString>& headers);
void parseBody(QxtMailMessagePrivate* msg);
void parseEntity(const QByteArray& buffer, QHash<QString,QString>& headers, QString& body);
QxtMailAttachment* parseAttachment(const QHash<QString,QString>& headers, const QString& body, QString& filename);
QString unfoldValue(QStringList& folded);
QString decode(const QString& charset, const QString& encoding, const QString& encoded);
};
QxtMailMessage::QxtMailMessage()
{
qxt_d = new QxtMailMessagePrivate;
}
QxtMailMessage::QxtMailMessage(const QxtMailMessage& other) : qxt_d(other.qxt_d)
{
// trivial copy constructor
}
QxtMailMessage::QxtMailMessage(const QString& sender, const QString& recipient)
{
qxt_d = new QxtMailMessagePrivate;
setSender(sender);
addRecipient(recipient);
}
/*!
Constructs a new QxtMailMessage object from a \a buffer that conforms to RFC 2822 and the MIME related RFCs.
*/
QxtMailMessage::QxtMailMessage(const QByteArray& buffer)
{
QxtRfc2822Parser parser;
qxt_d = parser.parse(buffer);
}
QxtMailMessage::~QxtMailMessage()
{
// trivial destructor
}
QxtMailMessage& QxtMailMessage::operator=(const QxtMailMessage & other)
{
qxt_d = other.qxt_d;
return *this;
}
QString QxtMailMessage::sender() const
{
return qxt_d->sender;
}
void QxtMailMessage::setSender(const QString& a)
{
qxt_d->sender = a;
}
QString QxtMailMessage::subject() const
{
return qxt_d->subject;
}
void QxtMailMessage::setSubject(const QString& a)
{
qxt_d->subject = a;
}
QString QxtMailMessage::body() const
{
return qxt_d->body;
}
void QxtMailMessage::setBody(const QString& a)
{
qxt_d->body = a;
}
QStringList QxtMailMessage::recipients(QxtMailMessage::RecipientType type) const
{
if (type == Bcc)
return qxt_d->rcptBcc;
if (type == Cc)
return qxt_d->rcptCc;
return qxt_d->rcptTo;
}
void QxtMailMessage::addRecipient(const QString& a, QxtMailMessage::RecipientType type)
{
if (type == Bcc)
qxt_d->rcptBcc.append(a);
else if (type == Cc)
qxt_d->rcptCc.append(a);
else
qxt_d->rcptTo.append(a);
}
void QxtMailMessage::removeRecipient(const QString& a)
{
qxt_d->rcptTo.removeAll(a);
qxt_d->rcptCc.removeAll(a);
qxt_d->rcptBcc.removeAll(a);
}
QHash<QString, QString> QxtMailMessage::extraHeaders() const
{
return qxt_d->extraHeaders;
}
QString QxtMailMessage::extraHeader(const QString& key) const
{
return qxt_d->extraHeaders[key.toLower()];
}
bool QxtMailMessage::hasExtraHeader(const QString& key) const
{
return qxt_d->extraHeaders.contains(key.toLower());
}
void QxtMailMessage::setExtraHeader(const QString& key, const QString& value)
{
qxt_d->extraHeaders[key.toLower()] = value;
}
void QxtMailMessage::setExtraHeaders(const QHash<QString, QString>& a)
{
QHash<QString, QString>& headers = qxt_d->extraHeaders;
headers.clear();
foreach(const QString& key, a.keys())
{
headers[key.toLower()] = a[key];
}
}
void QxtMailMessage::removeExtraHeader(const QString& key)
{
qxt_d->extraHeaders.remove(key.toLower());
}
QHash<QString, QxtMailAttachment> QxtMailMessage::attachments() const
{
return qxt_d->attachments;
}
QxtMailAttachment QxtMailMessage::attachment(const QString& filename) const
{
return qxt_d->attachments[filename];
}
void QxtMailMessage::addAttachment(const QString& filename, const QxtMailAttachment& attach)
{
if (qxt_d->attachments.contains(filename))
{
qWarning() << "QxtMailMessage::addAttachment: " << filename << " already in use";
int i = 1;
while (qxt_d->attachments.contains(filename + "." + QString::number(i)))
{
i++;
}
qxt_d->attachments[filename+"."+QString::number(i)] = attach;
}
else
{
qxt_d->attachments[filename] = attach;
}
}
void QxtMailMessage::removeAttachment(const QString& filename)
{
qxt_d->attachments.remove(filename);
}
QByteArray qxt_fold_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix)
{
QByteArray rv = "";
QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value))
{
bool firstWord = true;
foreach(const QByteArray& word, value.toLatin1().split(' '))
{
if (line.size() > 78)
{
rv = rv + line + "\r\n";
line.clear();
}
if (firstWord)
line += word;
else
line += " " + word;
firstWord = false;
}
}
else
{
// The text cannot be losslessly encoded as Latin-1. Therefore, we
// must use quoted-printable or base64 encoding. This is a quick
// heuristic based on the first 100 characters to see which
// encoding to use.
QByteArray utf8 = value.toUtf8();
int ct = utf8.length();
int nonAscii = 0;
for (int i = 0; i < ct && i < 100; i++)
{
if (QXT_MUST_QP(utf8[i])) nonAscii++;
}
if (nonAscii > 20)
{
// more than 20%-ish non-ASCII characters: use base64
QByteArray base64 = utf8.toBase64();
ct = base64.length();
line += "=?utf-8?b?";
for (int i = 0; i < ct; i += 4)
{
if (line.length() > 72)
{
rv += line + "?=\r\n";
line = " =?utf-8?b?";
}
line = line + base64.mid(i, 4);
}
}
else
{
// otherwise use Q-encoding
line += "=?utf-8?q?";
for (int i = 0; i < ct; i++)
{
if (line.length() > 73)
{
rv += line + "?=\r\n";
line = " =?utf-8?q?";
}
if (QXT_MUST_QP(utf8[i]) || utf8[i] == ' ')
{
line += "=" + utf8.mid(i, 1).toHex().toUpper();
}
else
{
line += utf8[i];
}
}
}
line += "?="; // end encoded-word atom
}
return rv + line + "\r\n";
}
QByteArray QxtMailMessage::rfc2822() const
{
// Use quoted-printable if requested
bool useQuotedPrintable = (extraHeader("Content-Transfer-Encoding").toLower() == "quoted-printable");
// Use base64 if requested
bool useBase64 = (extraHeader("Content-Transfer-Encoding").toLower() == "base64");
// Check to see if plain text is ASCII-clean; assume it isn't if QP or base64 was requested
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
bool bodyIsAscii = latin1->canEncode(body()) && !useQuotedPrintable && !useBase64;
QHash<QString, QxtMailAttachment> attach = attachments();
QByteArray rv;
if (!sender().isEmpty() && !hasExtraHeader("From"))
{
rv += qxt_fold_mime_header("From", sender(), latin1);
}
if (!qxt_d->rcptTo.isEmpty())
{
rv += qxt_fold_mime_header("To", qxt_d->rcptTo.join(", "), latin1);
}
if (!qxt_d->rcptCc.isEmpty())
{
rv += qxt_fold_mime_header("Cc", qxt_d->rcptCc.join(", "), latin1);
}
if (!subject().isEmpty())
{
rv += qxt_fold_mime_header("Subject", subject(), latin1);
}
if (!bodyIsAscii)
{
if (!hasExtraHeader("MIME-Version") && !attach.count())
rv += "MIME-Version: 1.0\r\n";
// If no transfer encoding has been requested, guess.
// Heuristic: If >20% of the first 100 characters aren't
// 7-bit clean, use base64, otherwise use Q-P.
if(!bodyIsAscii && !useQuotedPrintable && !useBase64)
{
QString b = body();
int nonAscii = 0;
int ct = b.length();
for (int i = 0; i < ct && i < 100; i++)
{
if (QXT_MUST_QP(b[i])) nonAscii++;
}
useQuotedPrintable = !(nonAscii > 20);
useBase64 = !useQuotedPrintable;
}
}
if (attach.count())
{
if (qxt_d->boundary.isEmpty())
qxt_d->boundary = QUuid::createUuid().toString().toLatin1().replace("{", "").replace("}", "");
if (!hasExtraHeader("MIME-Version"))
rv += "MIME-Version: 1.0\r\n";
if (!hasExtraHeader("Content-Type"))
rv += "Content-Type: multipart/mixed; boundary=" + qxt_d->boundary + "\r\n";
}
else if (!bodyIsAscii && !hasExtraHeader("Content-Transfer-Encoding"))
{
if (!useQuotedPrintable)
{
// base64
rv += "Content-Transfer-Encoding: base64\r\n";
}
else
{
// quoted-printable
rv += "Content-Transfer-Encoding: quoted-printable\r\n";
}
}
foreach(const QString& r, qxt_d->extraHeaders.keys())
{
if ((r.toLower() == "content-type" || r.toLower() == "content-transfer-encoding") && attach.count())
{
// Since we're in multipart mode, we'll be outputting this later
continue;
}
rv += qxt_fold_mime_header(r.toLatin1(), extraHeader(r), latin1);
}
rv += "\r\n";
if (attach.count())
{
// we're going to have attachments, so output the lead-in for the message body
rv += "This is a message with multiple parts in MIME format.\r\n";
rv += "--" + qxt_d->boundary + "\r\nContent-Type: ";
if (hasExtraHeader("Content-Type"))
rv += extraHeader("Content-Type") + "\r\n";
else
rv += "text/plain; charset=UTF-8\r\n";
if (hasExtraHeader("Content-Transfer-Encoding"))
{
rv += "Content-Transfer-Encoding: " + extraHeader("Content-Transfer-Encoding") + "\r\n";
}
else if (!bodyIsAscii)
{
if (!useQuotedPrintable)
{
// base64
rv += "Content-Transfer-Encoding: base64\r\n";
}
else
{
// quoted-printable
rv += "Content-Transfer-Encoding: quoted-printable\r\n";
}
}
rv += "\r\n";
}
if (bodyIsAscii)
{
QByteArray b = latin1->fromUnicode(body());
int len = b.length();
QByteArray line = "";
QByteArray word = "";
for (int i = 0; i < len; i++)
{
if (b[i] == '\n' || b[i] == '\r')
{
if (line.isEmpty())
{
line = word;
word = "";
}
else if (line.length() + word.length() + 1 <= 78)
{
line = line + ' ' + word;
word = "";
}
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
if ((b[i+1] == '\n' || b[i+1] == '\r') && b[i] != b[i+1])
{
// If we're looking at a CRLF pair, skip the second half
i++;
}
line = word;
}
else if (b[i] == ' ')
{
if (line.length() + word.length() + 1 > 78)
{
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = word;
}
else if (line.isEmpty())
{
line = word;
}
else
{
line = line + ' ' + word;
}
word = "";
}
else
{
word += b[i];
}
}
if (line.length() + word.length() + 1 > 78)
{
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = word;
}
else if (!word.isEmpty())
{
line += ' ' + word;
}
if(!line.isEmpty()) {
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
}
}
else if (useQuotedPrintable)
{
QByteArray b = body().toUtf8();
int ct = b.length();
QByteArray line;
for (int i = 0; i < ct; i++)
{
if(b[i] == '\n' || b[i] == '\r')
{
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = "";
if ((b[i+1] == '\n' || b[i+1] == '\r') && b[i] != b[i+1])
{
// If we're looking at a CRLF pair, skip the second half
i++;
}
}
else if (line.length() > 74)
{
rv += line + "=\r\n";
line = "";
}
if (QXT_MUST_QP(b[i]))
{
line += "=" + b.mid(i, 1).toHex().toUpper();
}
else
{
line += b[i];
}
}
if(!line.isEmpty()) {
if(line[0] == '.')
rv += ".";
rv += line + "\r\n";
}
}
else /* base64 */
{
QByteArray b = body().toUtf8().toBase64();
int ct = b.length();
for (int i = 0; i < ct; i += 78)
{
rv += b.mid(i, 78) + "\r\n";
}
}
if (attach.count())
{
foreach(const QString& filename, attach.keys())
{
rv += "--" + qxt_d->boundary + "\r\n";
rv += qxt_fold_mime_header("Content-Disposition", QDir(filename).dirName(), latin1, "attachment; filename=");
rv += attach[filename].mimeData();
}
rv += "--" + qxt_d->boundary + "--\r\n";
}
return rv;
}
/*!
Constructs a new QxtMailMessage object from a \a buffer that conforms to RFC 2822 and the MIME related RFCs.
*/
QxtMailMessage QxtMailMessage::fromRfc2822(const QByteArray& buffer)
{
QxtMailMessage rv;
QxtRfc2822Parser parser;
rv.qxt_d = parser.parse(buffer);
return rv;
}
QxtMailMessagePrivate* QxtRfc2822Parser::parse(const QByteArray& buffer)
{
QxtMailMessagePrivate* rv = new QxtMailMessagePrivate();
parseEntity(buffer, rv->extraHeaders, rv->body);
parseBody(rv);
return rv;
}
void QxtRfc2822Parser::parseEntity(const QByteArray& buffer, QHash<QString,QString>& headers, QString& body)
{
int pos = 0;
int crlfPos = 0;
QByteArray line;
currentHeaderKey = QString();
currentHeaderValue.clear();
state = Headers;
while (true)
{
crlfPos = buffer.indexOf("\r\n", pos);
if (crlfPos == -1)
{
break;
}
if (state == Headers && crlfPos == pos) // double CRLF reached: end of headers section
{
state = Body;
parseHeader("", headers); // to store the header currently being parsed (last one)
pos += 2;
continue;
}
line = buffer.mid(pos, crlfPos - pos);
pos = crlfPos + 2;
switch(state)
{
case Headers:
parseHeader(line, headers);
break;
case Body:
body.append(line + "\r\n");
break;
default:
break;
}
}
}
void QxtRfc2822Parser::parseHeader(const QByteArray& line, QHash<QString, QString>& headers )
{
QRegExp spRe("^[ \\t]");
QRegExp hdrRe("^([!-9;-~]+):[ \\t](.*)$");
if (spRe.indexIn(line) == 0) // continuation line
{
currentHeaderValue.append(line);
}
else
{
// starting a new header field. Store the current one before
if (!currentHeaderKey.isEmpty())
{
QString value = unfoldValue(currentHeaderValue);
headers[currentHeaderKey.toLower()] = value;
currentHeaderKey = QString();
currentHeaderValue.clear();
}
if (hdrRe.exactMatch(line))
{
currentHeaderKey = hdrRe.cap(1);
currentHeaderValue.append(hdrRe.cap(2));
} // else: empty or malformed header line. Ignore.
}
}
// extract the attachments from a multipart body
// proceed only one level deep
// future plans may involve nested parts and dealing with inline parts too
void QxtRfc2822Parser::parseBody(QxtMailMessagePrivate* msg)
{
QString& body = msg->body;
if (!msg->extraHeaders.contains("content-type")) return;
QString contentType = msg->extraHeaders["content-type"];
if (!contentType.indexOf("multipart",0,Qt::CaseInsensitive) == 0) return;
// extract the boundary delimiter
QRegExp boundaryRe("boundary=\"?([^\"]*)\"?(?=;|$)");
if (boundaryRe.indexIn(contentType) == -1)
{
qDebug("Boundary regexp didn't match for %s", contentType.toLatin1().data());
return;
}
QString boundary = boundaryRe.cap(1);
// qDebug("Boundary=%s", boundary.toLatin1().data());
QRegExp bndRe(QString("(^|\\r?\\n)--%1(--)?[ \\t]*\\r?\\n").arg(QRegExp::escape(boundary))); // find boundary delimiters in the body
// qDebug("search for %s", bndRe.pattern().toLatin1().data());
if (!bndRe.isValid())
{
qDebug("regexp %s not valid ! %s", bndRe.pattern().toLatin1().data(), bndRe.errorString().toLatin1().data());
}
// keep track of the position of two consecutive boundary delimiters:
// begin* is the position of the delimiter first character,
// end* is the position of the first character of the part following it.
int beginFirst = 0;
int endFirst = 0;
int beginSecond = 0;
int endSecond = 0;
while(bndRe.indexIn(body, endSecond) != -1)
{
beginSecond = bndRe.pos() + bndRe.cap(1).length(); // add length of preceding line break, if any
endSecond = bndRe.pos() + bndRe.matchedLength();
// qDebug("%d captures", bndRe.numCaptures());
// foreach(QString capture, bndRe.capturedTexts())
// {
// qDebug("->%s<-", capture.toLatin1().data());
// }
// qDebug("beginFirst = %d\nendFirst = %d\nbeginSecond = %d\nendSecond = %d", beginFirst, endFirst, beginSecond, endSecond);
if (endFirst != 0)
{
// handle part here:
QByteArray part = body.mid(endFirst, beginSecond - endFirst).toLocal8Bit();
QHash<QString,QString> partHeaders;
QString partBody;
parseEntity(part, partHeaders, partBody);
// qDebug("Part headers:");
// foreach (QString key, partHeaders.keys())
// {
// qDebug("%s: %s", key.toLatin1().data(), partHeaders[key].toLatin1().data());
// }
// qDebug("Part body:\n%s", partBody.toLatin1().data());
if (partHeaders.contains("content-disposition") && partHeaders["content-disposition"].indexOf("attachment;") == 0)
{
// qDebug("Attachment!");
QString filename;
QxtMailAttachment* attachment = parseAttachment(partHeaders, partBody, filename);
if (attachment)
{
msg->attachments.insert(filename, *attachment);
delete attachment;
}
// strip part from body
body.replace(beginFirst, beginSecond - beginFirst, "");
beginSecond = beginFirst;
endSecond = endFirst;
}
}
beginFirst = beginSecond;
endFirst = endSecond;
}
}
QString QxtRfc2822Parser::unfoldValue(QStringList& folded)
{
QString unfolded;
QRegExp encRe("=\\?([^? \\t]+)\\?([qQbB])\\?([^? \\t]+)\\?="); // search for an encoded word
QStringList::iterator i;
for (i = folded.begin(); i != folded.end(); ++i)
{
int offset = 0;
while (encRe.indexIn(*i, offset) != -1)
{
QString decoded = decode(encRe.cap(1), encRe.cap(2).toLower(), encRe.cap(3));
i->replace(encRe.pos(), encRe.matchedLength(), decoded); // replace encoded word with decoded one
offset = encRe.pos() + decoded.length(); // set offset after the inserted decoded word
}
}
unfolded = folded.join("");
return unfolded;
}
QString QxtRfc2822Parser::decode(const QString& charset, const QString& encoding, const QString& encoded)
{
QString rv;
QByteArray buf;
if (encoding == "q")
{
QByteArray src = encoded.toLatin1();
int len = src.length();
for (int i = 0; i < len; i++)
{
if (src[i] == '_')
{
buf += 0x20;
}
else if (src[i] == '=')
{
if (i+2 < len)
{
buf += QByteArray::fromHex(src.mid(i+1,2));
i += 2;
}
}
else
{
buf += src[i];
}
}
}
else if (encoding == "b")
{
buf = QByteArray::fromBase64(encoded.toLatin1());
}
QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1());
if (codec)
{
rv = codec->toUnicode(buf);
}
return rv;
}
QxtMailAttachment* QxtRfc2822Parser::parseAttachment(const QHash<QString,QString>& headers, const QString& body, QString& filename)
{
static int count = 1;
QByteArray content;
QRegExp filenameRe(";\\s+filename=\"?([^\"]*)\"?(?=;|$)");
if (filenameRe.indexIn(headers["content-disposition"]) != -1)
{
filename = filenameRe.cap(1);
}
else
{
filename = QString("attachment%1").arg(count);
}
// qDebug("Attachment %s", filename.toLocal8Bit().data());
QString ct;
if (headers.contains("content-type"))
{
ct = headers["content-type"];
}
else
{
ct = "application/octet-stream";
}
QString cte;
if (headers.contains("content-transfer-encoding"))
{
cte = headers["content-transfer-encoding"].toLower();
// qDebug("Content-Transfer-Encoding: %s", cte.toLatin1().data());
}
if ( cte == "base64")
{
content = QByteArray::fromBase64(body.toLatin1());
}
else if (cte == "quoted-printable")
{
QByteArray src = body.toLatin1();
int len = src.length();
QTextStream dest(&content);
for (int i = 0; i < len; i++)
{
if (src.mid(i,2) == "\r\n")
{
dest << endl;
i++;
}
else if (src[i] == '=')
{
if (i+2 < len)
{
if (src.mid(i+1,2) == "\r\n") // soft line break; skip
{
i +=2;
}
else
{
dest << QByteArray::fromHex(src.mid(i+1,2));
i += 2;
}
}
}
else
{
dest << src[i];
}
}
}
else // assume 7bit or 8bit
{
content = body.toLocal8Bit();
if (isTextMedia(ct))
{
content.replace("\r\n","\n");
}
}
QxtMailAttachment* rv = new QxtMailAttachment(content, ct);
rv->setExtraHeaders(headers);
return rv;
}
// gives only a hint, based on content-type value.
// takes value of Content-Type header field as parameter
// return true if the content-type corresponds to textual data (text/*, application/xml...)
// and false if unsure, so don't interpret a 'false' response as 'it's binary data...
bool isTextMedia(const QString& contentType)
{
// extract media type/sub-type part (e.g. text/plain, image/png...)
QRegExp mtRe("^([^;/]*)/([^;/]*)(?=;|$)");
if (mtRe.indexIn(contentType) == -1)
{
qWarning("failed to parse %s for type/subtype", contentType.toLatin1().data());
return false;
}
QString type = mtRe.cap(1).toLower();
QString subtype = mtRe.cap(2).toLower();
if (type == "text") return true;
if (type == "application" &&
(subtype == "x-csh" ||
subtype == "x-desktop" ||
subtype == "x-m4" ||
subtype == "x-perl" ||
subtype == "x-php" ||
subtype == "x-ruby" ||
subtype == "x-troff-man" ||
subtype == "xsd" ||
subtype == "xml-dtd" ||
subtype == "xml-external-parsed-entity" ||
subtype == "xslt+xml" ||
subtype == "xhtml+xml" ||
subtype == "pgp-keys" ||
subtype == "pgp-signature" ||
subtype == "javascript" ||
subtype == "ecmascript" ||
subtype == "docbook+xml" ||
subtype == "xml" ||
subtype == "html" ||
subtype == "x-shellscript")) return true;
if (type == "image" && subtype == "svg+xml") return true;
return false;
}