Source file: src/addresses.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 "main.h"
#include "addresses.h"
#include "db.h"
#include "sqlstream.h"
//static
QString
mail_address::part_string (const char* start, int len)
{
QString s(start);
s.truncate(len);
return s;
}
//static
QString
mail_address::parse_extract_email(const QString line)
{
std::list<QString> emails;
std::list<QString> names;
ExtractAddresses(line, emails, names);
if (emails.size()==1)
return emails.front();
else
return QString::null;
}
int
mail_address::ExtractAddresses(const QString& addr,
std::list<QString>& result,
std::list<QString>& result1)
{
QByteArray qba = addr.toLatin1();
return ExtractAddresses(qba.constData(), result, result1);
}
/*
Hand made parser
Currently recognizes:
foo@here.org
foo@here.org (Foo J. Bar)
<foo@here.org>
Foo Bar <foo@here.org>
'Foo Bar' <foo@here.org>
"Foo Bar" <foo@here.org>
*/
/*static*/
int
mail_address::ExtractAddresses(const char* addr,
std::list<QString>& result,
std::list<QString>& result1)
{
if (!addr) // happens if addr is the result of QString::null.latin1();
return 0;
// result => emails
// result1 => names
const char* start_addr;
const char* start1_addr;
const char* end_addr;
const char* end1_addr;
int c;
char enclose_char=0;
int err=0;
c=*addr++;
while (c) {
switch(c) {
case ' ':
case '\t':
case '\n':
case '\r':
case ',':
c=*addr++;
break; // address separator
case '\0':
break;
case '<':
// <foo@here.org> expected
start_addr=addr;
c=*addr++;
while (c && c!=' ' && c!='\t' && c!='>')
c=*addr++;
switch(c) {
case '>':
result.push_back (part_string (start_addr, addr-start_addr-1));
result1.push_back(QString(""));
c=*addr++;
break;
default:
err=1;
return err;
}
break;
case '\'':
case '"':
enclose_char=c;
start1_addr=addr;
c=*addr++;
while (c && c!=enclose_char)
c=*addr++;
if (!c) {
err=1;
return err;
}
result1.push_back (part_string (start1_addr,addr-start1_addr-1));
c=*addr++;
while (c==' ' || c=='\t')
c=*addr++;
if (!c) { // empty address: \".*\"([ \t])*
result.push_back(QString(""));
break;
}
if (c!='<') {
//err=2; // "foo bar" not followed by <foo@here.org>
//return err;
while ((c=*(++addr))!='<') {
if (!c) {
err=2;
return err;
}
}
addr++;
}
start_addr=addr;
c=*addr++;
while (c && c!='>')
c=*addr++;
if (!c) {
err=1;
return err;
}
result.push_back (part_string (start_addr, addr-start_addr-1));
c=*addr++;
break;
default:
int enclose=0; //(c=='\'');
start_addr=addr-1;
start1_addr=addr-1;
c=*addr++;
while (c && (enclose || c!=' ') && c!='\t' && c!=',') // && c!='\'')
c=*addr++;
if (!c || c==',') {
// email alone
result.push_back(part_string (start_addr, addr-start_addr-1));
result1.push_back(QString(""));
}
else {
// 'Name firstname <email>' or 'email (name firstname)'
// addr
end_addr = addr;
c=*addr++;
while (c==' ' || c=='\t')
c=*addr++;
switch(c) {
case '\0':
err=1;
return err;
case '<':
{
QString name1(start1_addr);
name1.truncate(addr-start1_addr-1);
// strip trailing white spaces from the name
result1.push_back (name1.trimmed());
}
start_addr=addr;
c=*addr++;
while (c && c!='>')
c=*addr++;
if (!c) {
err=1;
return err;
}
result.push_back (part_string (start_addr, addr-start_addr-1));
c=*addr++;
break;
case '(':
start1_addr=addr;
c=*addr++;
while (c && c!=')')
c=*addr++;
if (!c) {
err=1;
return err;
}
result.push_back(part_string(start_addr,end_addr-start_addr-1));
result1.push_back(part_string (start1_addr, addr-start1_addr-1));
c=*addr++;
break;
default: // Name firstname <email>
c=*addr++;
while (c && c!='<')
c=*addr++;
if (!c) {
err=1;
return err;
}
end1_addr=addr-1;
start_addr=addr;
c=*addr++;
while (c && c!='>')
c=*addr++;
if (!c) {
err=1;
return err;
}
result.push_back(part_string(start_addr,addr-start_addr-1));
result1.push_back(part_string(start1_addr, end1_addr-start1_addr-1));
c=*addr++;
break;
}
}
break;
}
}
return err;
}
/*
replace new lines in the addresses line by commas.
replace multiple new lines by a comma and spaces.
*/
void // static
mail_address::join_address_lines(QString& line)
{
int pos=0;
int lastpos=-1;
int len = line.length();
QString d; // destination
// skip starting \n+
while (pos<len && line.at(pos)=='\n') {
pos++;
}
// if the line is only \n+, null it
if (pos == len) {
line.truncate(0);
return;
}
while (pos<len) {
if (line.at(pos)=='\n' || line.at(pos)==',') {
lastpos=pos;
while (pos<len && (line.at(pos)=='\n' ||line.at(pos)==','))
pos++;
if (pos<len) {
d.append(", ");
}
}
else {
d.append(line.at(pos++));
}
}
line = d;
}
void
mail_address::normalize()
{
m_address = m_address.toLower();
}
QString
mail_address::email_and_name()
{
QString result;
if (m_name.isEmpty()) {
result = m_address;
}
else if (m_name.indexOf ('"') == -1) {
// preferred format: "john doe" <john.doe@sample.com>
result = QString("\"") + m_name + QString("\" <") + m_address +
QString(">");
}
else if (m_name.indexOf("()") == -1) {
// second preferred format: john.doe@sample.com (John Doe)
result = m_address + QString(" (") + m_name + QString(")");
}
else {
int pos = 0;
// remove the parenthesis from m_name
while ( (pos = m_name.indexOf("()", pos)) != -1) {
m_name.remove (pos, 1);
}
// and return the second preferred form
result = m_address + QString(" (") + m_name + QString(")");
}
return result;
}
// insert or update the address
bool
mail_address::store()
{
db_cnx db;
try {
if (m_id) {
sql_stream s("UPDATE addresses SET email_addr=:p1,name=:p2,notes=:p3,nickname=:p4,invalid=:p5 WHERE addr_id=:id", db);
s << m_address << m_name << m_notes << m_nickname << m_invalid;
/*
if (!m_date_last_sent.isEmpty())
s << m_date_last_sent;
else
s << sql_null();
*/
s << m_id;
}
else {
if (!db.next_seq_val("seq_addr_id", &m_id))
return false;
sql_stream s("INSERT INTO addresses(addr_id,email_addr,name,notes,nickname,invalid) VALUES (:p1,:p2,:p3,:p4,:p5, :p6)", db);
s << m_id << m_address << m_name << m_notes << m_nickname << m_invalid;
/*
if (!m_date_last_sent.isEmpty())
s << m_date_last_sent;
else
s << sql_null();
*/
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
/*
type="sent" or "recv"
*/
bool
mail_address::update_last_used(const QString type)
{
db_cnx db;
try {
if (!m_id) {
DBG_PRINTF(5,"warning: m_id=0\n");
return false;
}
if (type=="sent") {
sql_stream s("UPDATE addresses SET last_sent_to=now(), nb_sent_to=1+coalesce(nb_sent_to,0) WHERE addr_id=:id", db);
s << m_id;
}
else if (type=="recv") {
sql_stream s("UPDATE addresses SET last_recv_from=now() WHERE addr_id=:id", db);
s << m_id;
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
// lookup the email address in the addresses table
bool
mail_address::fetchByEmail(const QString& email, bool* found)
{
// TODO: make a cache
db_cnx db;
try {
sql_stream s("SELECT addr_id,name FROM addresses WHERE email_addr=lower(:p1)", db);
s << email;
if (!s.eof()) {
s >> m_id >> m_name;
m_address=email;
*found=true;
}
else
*found=false;
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
// lookup the email address in the addresses table
bool
mail_address::fetch_by_nickname(const QString& nick, bool* found)
{
// TODO: make a cache
db_cnx db;
try {
sql_stream s("SELECT addr_id,name,email_addr FROM addresses WHERE nickname=lower(:p1)", db);
s << nick;
if (!s.eof()) {
s >> m_id >> m_name >> m_address;
*found=true;
}
else
*found=false;
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
// make <email> be an alias to this.
void
mail_address::add_alias (const QString email)
{
db_cnx db;
try {
sql_stream s1("SELECT addr_id FROM addresses where email_addr=:p1", db);
s1 << email;
if (!s1.eof()) {
int addr_id;
s1 >> addr_id;
sql_stream s("UPDATE addresses SET owner_id=:p1 WHERE addr_id=:p2", db);
s << m_id << addr_id;
}
}
catch(db_excpt& p) {
DBEXCPT(p);
}
}
// make <email> to no longer be an alias to this.
void
mail_address::remove_alias (const QString email)
{
db_cnx db;
try {
sql_stream s1("SELECT addr_id FROM addresses where email_addr=:p1", db);
s1 << email;
if (!s1.eof()) {
int addr_id;
s1 >> addr_id;
sql_stream s("UPDATE addresses SET owner_id=null WHERE addr_id=:p2", db);
s << addr_id;
}
}
catch(db_excpt& p) {
DBEXCPT(p);
}
}
void
mail_address::fetch_details(const QString& email)
{
db_cnx db;
try {
sql_stream s("SELECT addr_id,name,nickname,nb_recv_from,notes,TO_CHAR(last_recv_from,'YYYYMMDDHH24MISS'),TO_CHAR(last_sent_to,'YYYYMMDDHH24MISS'),invalid,recv_pri,nb_sent_to FROM addresses WHERE email_addr=lower(:p1)", db);
s << email;
if (!s.eof()) {
QString sdate1, sdate2;
s >> m_id >> m_name >> m_nickname >> m_nb_from >> m_notes >> sdate1 >> sdate2 >> m_invalid >> m_recv_pri >> m_nb_to;
m_date_last_recv = date(sdate1);
m_date_last_sent = date(sdate2);
m_address=email;
}
else
m_id=0;
}
catch(db_excpt& p) {
DBEXCPT(p);
}
}
void
mail_address::fetch_aliases (std::list<mail_address>* alist)
{
db_cnx db;
alist->clear();
try {
sql_stream s1("SELECT addr_id FROM addresses WHERE email_addr=:p1", db);
s1 << m_address;
if (!s1.eof()) {
int id;
s1 >> id;
sql_stream s("SELECT addr_id,email_addr FROM addresses WHERE owner_id=:p1", db);
s << id;
while (!s.eof()) {
int id;
QString a_email;
s >> id >> a_email;
mail_address a;
a.set (a_email);
a.setId (id);
alist->push_back (a);
}
}
}
catch(db_excpt& p) {
DBEXCPT(p);
}
}
bool
mail_address_list::fetchLike(const QString pattern,
const QString order,
bool at_start)
{
QString query;
QString percent_pattern;
if (at_start)
percent_pattern = pattern + "%%";
else
percent_pattern = "%%" + pattern + "%%";
if (order == "last_recv_from") {
query = "SELECT addr_id,email_addr,nb_recv_from,nb_sent_to FROM addresses WHERE email_addr ilike :p1 AND last_recv_from IS NOT NULL ORDER BY last_recv_from desc";
}
else if (order == "last_sent_to") {
query = "SELECT a.addr_id,a.email_addr,a.nb_recv_from,a.nb_sent_to FROM addresses a WHERE email_addr ilike :p1 AND addr_id in (select addr_id from mail_addresses where addr_type=2) ORDER BY last_sent_to desc";
}
else {
query = "SELECT addr_id,email_addr,nb_recv_from,nb_sent_to FROM addresses WHERE email_addr ilike :p1";
}
db_cnx db;
try {
sql_stream s(query, db);
s << percent_pattern;
while (!s.eos()) {
int id;
QString email;
int nb_from,nb_to;
s >> id >> email >> nb_from >> nb_to;
mail_address addr;
addr.setId(id);
addr.set(email);
addr.set_nb_msgs(nb_from, nb_to);
push_back(addr);
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
/* Check if an email address has only one @ sign and us-ascii characters */
// static
bool
mail_address::basic_syntax_check(const QString email)
{
int cnt_at=0;
int l=email.length();
for (int i=0; i<l; i++) {
QChar c=email.at(i);
if (c=='@') {
if (++cnt_at > 1)
return false;
// regexp for a domain name
QRegExp re("^([A-Za-z0-9]([-a-zA-Z0-9]*[A-Za-z0-9])?\\.)+[A-Za-z][A-Za-z0-9]+$");
return (re.indexIn(email, i+1, QRegExp::CaretAtOffset)==i+1);
}
else {
if (c.unicode()<=0x20 || c.unicode()>=0x7f)
return false;
}
}
if (cnt_at==0)
return false;
return true;
}
/* Retrieve the contacts for which the email or one word from the
comments (name, firstname...) starts with 'substring' */
bool
mail_address_list::fetch_completions(const QString substring)
{
QString query=QString("SELECT addr_id,email_addr,name FROM addresses WHERE (last_sent_to is not null or last_recv_from is not null) AND (lower(name) ~ ('[[:<:]]'||lower(:p1)) OR email_addr ~ ('^'||lower(:p2))) AND coalesce(invalid,0)=0 ORDER BY greatest(last_sent_to,last_recv_from) DESC");
db_cnx db;
try {
sql_stream s(query, db);
s << substring << substring;
while (!s.eos()) {
int id;
QString email, name;
s >> id >> email >> name;
mail_address addr;
addr.setId(id);
addr.set(email);
addr.set_name(name);
push_back(addr);
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
bool
mail_address_list::fetch_from_substring(const QString substring)
{
QString query;
QString percent_pattern = "%" + substring + "%";
query = "SELECT addr_id,email_addr,name,TO_CHAR(last_recv_from,'YYYYMMDDHH24MISS'),TO_CHAR(last_sent_to,'YYYYMMDDHH24MISS') FROM addresses WHERE email_addr ilike :p1 OR name ilike :p2";
db_cnx db;
try {
sql_stream s(query, db);
s << percent_pattern << percent_pattern;
while (!s.eos()) {
int id;
QString email, name;
QString date1, date2;
s >> id >> email >> name >> date1 >> date2;
mail_address addr;
addr.setId(id);
addr.set(email);
addr.set_name(name);
addr.set_last_recv(date(date1));
addr.set_last_sent(date(date2));
push_back(addr);
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
bool
mail_address_list::fetchFromMail(mail_id_t mail_id, int/*mail_address::t_addrType */ addr_type)
{
bool result=true;
db_cnx db;
try {
sql_stream s("SELECT a.addr_id,a.email_addr,a.name"
" FROM addresses a, mail_addresses ma"
" WHERE ma.mail_id=:p1"
" AND a.addr_id=ma.addr_id"
" AND ma.addr_type=:p2", db);
s << mail_id << addr_type;
while (!s.eof()) {
mail_address addr;
QString email_addr;
QString name;
int addr_id;
s >> addr_id >> email_addr >> name;
addr.setId(addr_id);
addr.set(email_addr);
addr.set_name(name);
push_back (addr);
}
}
catch (db_excpt& p) {
DBEXCPT (p);
result = false;
}
return result;
}
bool
mail_address_list::fetch()
{
QString query;
QString percent_pattern;
percent_pattern = QString("%%") + m_search.m_email + "%%";
query = "SELECT addr_id,email_addr,nb_recv_from,nb_sent_to FROM addresses WHERE email_addr ilike :p1";
db_cnx db;
try {
sql_stream s(query, db);
s << percent_pattern;
while (!s.eos()) {
int id;
QString email;
int nb_from,nb_to;
s >> id >> email >> nb_from >> nb_to;
mail_address addr;
addr.setId(id);
addr.set(email);
addr.set_nb_msgs(nb_from, nb_to);
push_back(addr);
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
bool
mail_address::diff_update(const mail_address& to_update)
{
std::list<QString> fields;
std::list<QString> values;
if (to_update.m_nickname != m_nickname) {
fields.push_back("nickname");
values.push_back(to_update.m_nickname);
}
if (to_update.m_name != m_name) {
fields.push_back("name");
values.push_back(to_update.m_name);
}
if (to_update.m_notes != m_notes) {
fields.push_back("notes");
values.push_back(to_update.m_notes);
}
if (to_update.m_recv_pri != m_recv_pri) {
fields.push_back("recv_pri");
values.push_back(QString("%1").arg(to_update.m_recv_pri));
}
if (!fields.empty()) {
QString query = "UPDATE addresses SET ";
std::list<QString>::iterator it1 = fields.begin();
std::list<QString>::iterator it2 = values.begin();
int nvar=1;
for (; it1 != fields.end(); ++it1) {
if (nvar>1)
query.append(",");
query.append(*it1);
query.append("=");
query.append(QString(":p%1").arg(nvar++));
}
db_cnx db;
try {
query.append(" WHERE email_addr=:email");
sql_stream s(query, db);
for (; it2 != values.end(); ++it2) {
s << *it2;
}
s << m_address;
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
}
return true;
}
/*
Fetch <count> email addresses recently used in from or to recipients
if <count>=0, don't limit the number of rows fetched
<type>: 1=to, 2=from, 3=positive prio
ignore addresses before <offset> if offset is given
*/
bool
mail_address_list::fetch_recent(int type, int count/*=10*/, int offset/*=0*/)
{
QString query;
QString percent_pattern;
if (type == 3) {
query = "SELECT addr_id,email_addr,name,to_char(last_sent_to,'YYYYMMDDHH24MISS'),recv_pri FROM addresses WHERE recv_pri>0 ORDER BY recv_pri desc";
}
else if (type == 2) {
query = "SELECT addr_id,email_addr,name,to_char(last_recv_from,'YYYYMMDDHH24MISS'),recv_pri FROM addresses WHERE last_recv_from IS NOT null ORDER BY last_recv_from desc";
}
else if (type == 1) {
query = "SELECT addr_id,email_addr,name,to_char(last_sent_to,'YYYYMMDDHH24MISS'),recv_pri FROM addresses WHERE last_sent_to IS NOT NULL ORDER BY last_sent_to desc";
}
else {
throw "unknown type";
}
if (count) {
query += QString(" LIMIT %1").arg(count);
}
if (offset) {
query += QString(" OFFSET %1").arg(offset);
}
db_cnx db;
try {
sql_stream s(query, db);
while (!s.eos()) {
QString email;
QString name;
QString sdate;
int pri;
int id;
s >> id >> email >> name >> sdate >> pri;
mail_address addr;
addr.setId(id);
addr.set(email);
addr.set_name(name);
addr.m_recv_pri=pri;
date d(sdate);
if (type==2)
addr.set_last_recv(d);
else
addr.set_last_sent(d);
push_back(addr);
}
}
catch(db_excpt& p) {
DBEXCPT(p);
return false;
}
return true;
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files