Source file: src/mailheader.cpp
/* Copyright (C) 2004-2010 Daniel Verite
This file is part of Manitou-Mail (see http://www.manitou-mail.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include "main.h"
#include "db.h"
#include "config.h"
#include "mailheader.h"
#include "sqlstream.h"
#include <QTextCodec>
#include <QRegExp>
#ifndef __GNUG__
// for _alloca()
#include <malloc.h>
#endif
void
mail_header::make()
{
m_lines.truncate(0);
if (!m_from.isEmpty())
m_lines.append(QString("From: %1\n").arg(m_from));
if (!m_to.isEmpty())
m_lines.append(QString("To: %1\n").arg(m_to));
if (!m_cc.isEmpty())
m_lines.append(QString("Cc: %1\n").arg(m_cc));
if (!m_bcc.isEmpty())
m_lines.append(QString("Bcc: %1\n").arg(m_bcc));
if (!m_replyTo.isEmpty())
m_lines.append(QString("Reply-To: %1\n").arg(m_replyTo));
if (!m_subject.isEmpty())
m_lines.append(QString("Subject: %1\n").arg(m_subject));
if (!m_inReplyTo.isEmpty())
m_lines.append(QString("In-Reply-To: %1\n").arg(m_inReplyTo));
QString date;
int tz_offset=0;
db_cnx db;
try {
sql_stream s("select to_char(now(),:fmt), extract(timezone from now())", db);
s << "Dy, DD Mon YYYY HH24:MI:SS";
if (!s.eos()) {
s >> date >> tz_offset;
}
}
catch(db_excpt& p) {
DBEXCPT(p);
}
if (!date.isEmpty()) {
QString datetz;
datetz.sprintf(" %c%02d%02d", tz_offset>0?'+':'-', abs(tz_offset)/3600,
abs(tz_offset%3600)/60);
m_lines.append(QString("Date: %1\n").arg(date+datetz));
}
if (!m_messageId.isEmpty()) {
m_lines.append(QString("Message-Id: <%1>\n").arg(m_messageId));
}
m_lines.append(QString("X-Mailer: Manitou v%1\n").arg(VERSION));
if (!m_xface.isEmpty())
m_lines.append(QString("X-Face: %1\n").arg(m_xface));
}
// store from,to,cc,bcc addresses into the database
bool
mail_header::store_addresses()
{
bool res=true;
if (!m_from.isEmpty())
res &= store_addresses_list(m_from, (int)mail_address::addrFrom);
if (!m_to.isEmpty())
res &= store_addresses_list(m_to, (int)mail_address::addrTo);
if (!m_cc.isEmpty())
res &= store_addresses_list(m_cc, (int)mail_address::addrCc);
if (!m_bcc.isEmpty())
res &= store_addresses_list(m_bcc, (int)mail_address::addrBcc);
if (!m_replyTo.isEmpty())
res &= store_addresses_list(m_replyTo, (int)mail_address::addrReplyTo);
return res;
}
bool
mail_header::store_addresses_list(const QString& addr_list, int addr_type)
{
db_cnx db;
sql_stream s("INSERT INTO mail_addresses(mail_id,addr_id,addr_type,addr_pos) VALUES(:p1,:p2,:p3,:p4)", db);
std::list<QString> emails;
std::list<QString> names;
mail_address::ExtractAddresses(addr_list.toLatin1().constData(), emails, names);
bool found;
std::list<QString>::iterator it1 = emails.begin();
std::list<QString>::iterator it2 = names.begin();
for (int addr_pos = 0; it1 != emails.end(); addr_pos++) {
mail_address a;
QString s_addr=(*it1).toLower();
bool res=a.fetchByEmail(*(it1),&found);
if (!res)
return false;
if (!found) {
// create the address if it doesn't exist yet
a.set(s_addr);
a.set_name(*(it2));
if (!a.store())
return false;
}
if (addr_type == mail_address::addrTo) {
a.update_last_used("sent");
}
s << m_mail_id << a.id() << (int)addr_type << addr_pos;
it1++; it2++;
}
return true;
}
/* Return a string based on the To header field, in a format suitable to go
into mail.recipients */
QString
mail_header::recipients_list()
{
QString result;
std::list<QString> emails;
std::list<QString> names;
mail_address::ExtractAddresses(m_to.toLatin1().constData(), emails, names);
std::list<QString>::iterator it1 = emails.begin();
std::list<QString>::iterator it2 = names.begin();
for (; it1 != emails.end() && it2!=names.end(); ) {
mail_address a;
a.set((*it1).toLower());
a.set_name(*(it2));
if (!result.isEmpty())
result.append(',');
result.append(a.email_and_name());
it1++; it2++;
}
return result;
}
bool
mail_header::store()
{
make();
store_addresses();
db_cnx db;
sql_stream s("INSERT INTO header(mail_id,lines) VALUES (:p1, ':p2')",
db);
s << m_mail_id << m_lines;
return true;
}
bool
mail_header::fetch()
{
db_cnx db;
try {
sql_stream s("SELECT lines FROM header WHERE mail_id=:p1", db);
s << m_mail_id;
if (!s.eos()) {
s >> m_lines;
}
else
m_lines="";
}
catch(db_excpt& p) {
DBEXCPT(p);
m_lines="";
return false;
}
return true;
}
static char
to_hex(char c1, char c2)
{
int i;
if (c1>='0' && c1<='9')
i=(c1-'0')<<4;
else if (c1>='A' && c1<='F')
i=(c1-'A'+10)<<4;
else if (c1>='a' && c1<='f')
i=(c1-'a'+10)<<4;
else throw 6;
if (c2>='0' && c2<='9')
i+=c2-'0';
else if (c2>='A' && c2<='F')
i+=c2-'A'+10;
else if (c2>='a' && c2<='f')
i+=c2-'a'+10;
else throw 6;
return i;
}
static int
append_decoded_qp(const QString& s, char* buf)
{
int jb=0;
for (int j=0; j<s.length(); j++) {
char c=s.at(j).toAscii();
if (c=='=') {
if (j<s.length()-2) {
try {
char c1=to_hex(s.at(j+1).toAscii(), s.at(j+2).toAscii());
buf[jb++]=c1;
j+=2;
continue;
}
catch(int) {
/* in case of QP decoding error, we fall into the normal case
as if it wasn't QP */
}
}
}
else if (c=='_') {
c=' ';
}
buf[jb++]=c;
}
return jb;
}
/*
src=base64 source
dest=destination
size=pointer to the number of input bytes to be processed.
On return, *size contains the number of bytes that still need
to be processed (should be between 0 and 3)
*/
static int
decode_base64(const char* src, char* dest, int* size)
{
int i=0;
char c;
int pad=0;
char* initial_dest=dest;
int s=*size;
int v;
int val=0;
while (s>0) {
c=*src++;
s--;
if (c=='\n' || c=='\r')
continue;
if (c>='A' && c<='Z') {
v=c-'A';
}
else if (c>='a' && c<='z')
v=c-'a'+26;
else if (c>='0' && c<='9')
v=c-'0'+52;
else if (c=='+')
v=62;
else if (c=='/')
v=63;
else if (c=='=') {
v=0;
if (++pad>2) {
return -1; /* no more than 2 '=' are allowed */
}
}
else
return -1; /* non-base64 input character */
val=(val<<6)|v;
if (++i==4) {
i=0;
*dest++=(val&0xFF0000)>>16;
if (pad<2) *dest++=(val&0x00FF00)>>8;
if (pad<1) *dest++=(val&0x0000FF);
val=0;
}
}
*size=i;
return dest-initial_dest;
}
static int
append_decoded_b64(const QString& s, char* buf, int sz)
{
return decode_base64(s.toLatin1().constData(), buf, &sz);
}
//static
void
mail_header::decode_line(QString& s)
{
int pos=s.indexOf("=?");
if (pos<0)
return;
QString dline;
QRegExp rx("=\\?([^\\?]+)\\?(q|Q|b|B)\\?([^\\?]*)\\?=");
int r=rx.indexIn(s, pos);
int end_rx=0;
pos=0;
#ifndef __GNUG__
int maxbufsz=0;
#endif
while (r>=0) {
if (r-pos>0) {
// append the contents before the encoded word
// except if it's spaces only
QString before=s.mid(pos,r-pos);
// printf("\tend_rx=%d before=%s\n", end_rx, before.latin1());
if (end_rx>0) {
// when between two encoded words, ignore the contents
// if it contains only spaces (rfc822 'linear-white-space')
for (int i=0; i<before.length(); i++) {
QChar c=before.at(i);
if (c!=' ' && c!='\t' && c!='\n') {
dline.append(before);
break;
}
}
}
else {
// printf("\tappend %s\n", before.latin1());
dline.append(before);
}
}
//printf("\tmatch at r=%d\n", r);
int rxsz=rx.matchedLength();
QStringList l=rx.capturedTexts();
QStringList::Iterator it = l.begin();
++it;
//printf("\tcodecname=%s\n", (*it).latin1());
QTextCodec* codec=QTextCodec::codecForName((*it).toLatin1());
++it;
char enctype=(*it).at(0).toUpper().toAscii();
++it;
int bufsz=(*it).length();
#ifdef __GNUG__
char buf[bufsz+1];
#else
char* buf;
if (bufsz>maxbufsz) {
buf = (char*)_alloca(bufsz+1);
maxbufsz = bufsz;
}
#endif
//printf("\t*it=%s\n", (*it).latin1());
int jb;
if (enctype=='Q') {
jb=append_decoded_qp((*it), buf);
}
else if (enctype=='B') {
jb=append_decoded_b64((*it), buf, bufsz);
}
else {
// avoid a compiler warning (this code should never be reached)
jb=0;
}
if (codec) {
dline.append(codec->toUnicode(buf, jb));
}
else {
//printf("\tNO CODEC!\n");
buf[bufsz]='\0';
dline.append(buf);
}
end_rx=r+rxsz;
pos=r+rxsz;
r=rx.indexIn(s, pos);
}
dline.append(s.mid(end_rx));
s=dline;
}
/*
Convert an encoded header into a decoded form:
- internal QString encoding (unicode)
- lines unfolded
*/
// static
void
mail_header::decode_rfc822(const QString src, QString& dest)
{
QString curline;
int len=src.length();
for (int i=0; i<len; i++) {
char c=src.at(i).toAscii();
switch(c) {
case '\n':
if (i+1<len) {
char c1=src.at(i+1).toAscii();
if (c1!=' ' && c1!='\t') {
curline.append(c);
}
else {
curline.append(' ');
i++;
while (i<len && (c1==' ' || c1=='\t')) {
c1=src.at(i).toAscii();
i++;
}
if (i<len) {
i--;
curline.append(c1);
}
}
}
break;
default:
curline.append(c);
break;
}
}
dest=curline;
decode_line(dest);
}
/*
Compute the length of a header from a rfc822 message
*/
//static
uint
mail_header::header_length(const char* rawmsg)
{
const char* p=rawmsg;
char c;
char last_c='\0';
int idx=0;
while ((c=*p++)) {
if (c=='\n') {
if (last_c=='\n')
return p-rawmsg; // \n\n => end of header
if (last_c=='\r' && idx>3 && *(p-2)=='\n' && *(p-3)=='\r') {
// \r\n\r\n => end of header
return p-rawmsg;
}
}
last_c=c;
idx++;
}
return 0;
}
bool
mail_header::fetch_raw()
{
db_cnx db;
try {
m_raw.truncate(0);
db.begin_transaction();
sql_stream s("SELECT mail_text FROM raw_mail WHERE mail_id=:p1", db);
s << m_mail_id;
Oid lobjId;
if (!s.eos()) {
s >> lobjId;
}
else
return false;
PGconn* c=db.connection();
int lobj_fd = lo_open(c, lobjId, INV_READ);
if (lobj_fd < 0) {
return false;
}
unsigned char data[8192+1];
unsigned int nread;
bool end_header=false;
bool has_8_bit=false;
data[sizeof(data)-1]='\0';
do {
nread = lo_read(c, lobj_fd, (char*)data, sizeof(data)-1);
// check if the contents are 8 bit clean
char lastc=0;
unsigned int j;
for (j=0; j<nread && !end_header; j++) {
if (data[j]>=0x80)
has_8_bit=true;
if (lastc=='\n' && data[j]=='\n') {
end_header=true;
data[j]='\0';
}
lastc=data[j];
}
QTextCodec* codec;
// if not 8 bit clean, apply an arbitrary codec
if (has_8_bit && (codec=QTextCodec::codecForName("iso-8859-15"))) {
m_raw.append(codec->toUnicode((char*)data, j));
#if QT_VERSION<0x040000
/* it's not clear whether it's desirable or not to delete the
codec under Qt3, but it's not possible under Qt4 anyway,
the destructor being protected */
delete codec;
#endif
}
else {
m_raw.append((char*)data);
}
} while (!end_header && nread==sizeof(data));
lo_close(c, lobj_fd);
db.commit_transaction();
}
catch(db_excpt& p) {
db.rollback_transaction();
DBEXCPT(p);
return false;
}
return true;
}
void
mail_header::format(QString& sOutput, const QString& h1)
{
QString h;
decode_rfc822(h1, h);
uint nPos=0;
uint nEnd=h.length();
QString hfontsz_o; // <big> or empty
QString hfontsz_c; // </big> or empty
// show all headers
while (nPos<nEnd) {
int nPosEh=0; // end of header name
// position of end of current line
int nPosLf=h.indexOf('\n',nPos);
if (nPosLf==-1)
nPosLf=nEnd;
if (h.mid(nPos,1)!=" " && h.mid(nPos,1)!="\t" && h.mid(nPos,5)!="From "
&& (nPosEh=h.indexOf(':',nPos))>0)
{
QString sContents=h.mid(nPosEh+1, nPosLf-nPosEh-1);
sContents.replace(QRegExp("<"), "<");
sContents.replace(QRegExp(">"), ">");
sContents.replace (QRegExp ("\t"), " ");
sOutput += hfontsz_o + QString("<font color=\"blue\">") +
h.mid(nPos,nPosEh-nPos+1) +
"</font>" + sContents + hfontsz_c+"<br>";
}
else {
sOutput += hfontsz_o + h.mid(nPos,nPosLf-nPos+1) + hfontsz_c + "<br>";
}
nPos=nPosLf+1;
}
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files