You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

742 lines
20 KiB

/**
* vcard.cpp
*
* Copyright (c) 2000 George Staikos <staikos@kde.org>
*
* This program 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.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
//
// VCard class to handle creation and parsing of Netscape
// vcards as per RFC 2426 (and possibly more)
//
// FIXME: do proper CRLF/CR handling
// FIXME: handle TYPE=x,y,z qualifiers
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "vcard.h"
#include <klocale.h>
#include <qregexp.h>
/*
Parser structure:
vcard := <begin><body><end> | <begin><end>
body := <line><body> | <line>
begin := begin:vcard
end := end:vcard
line := <name>;<qualifiers>:<value> | <name>:<value>
qualifiers := <qualifier> | <qualifier>;<qualifiers>
name :=
qualifier :=
value :=
*/
// This code is screaming "come do me in PERL!"
//static QValueList<QString> tokenizeBy(const QString& str, char tok) {
static QValueList<QString> tokenizeBy(const QString& str, const QRegExp& tok, bool keepEmpties = false) {
QValueList<QString> tokens;
unsigned int head, tail;
unsigned int length = str.length();
if (length < 1) return tokens;
if (length == 1) {
tokens.append(str);
return tokens;
}
for(head = 0, tail = 0; tail < length-1; head = tail+1) {
QString thisline;
tail = str.find(tok, head);
if (tail > length) // last token - none at end
tail = length;
if (tail-head > 0 || keepEmpties) { // it has to be at least 1 long!
thisline = str.mid(head, tail-head);
tokens.append(thisline);
}
}
return tokens;
}
VCard::VCard() {
_vcdata = new QValueList<VCardLine>;
}
VCard::~VCard() {
delete _vcdata;
}
VCard::VCard(QValueList<VCardLine> *_vcd) : _vcdata(_vcd) {
}
#define VC_ERR_NO_BEGIN 1
#define VC_ERR_NO_END 2
#define VC_ERR_INVALID_LINE 3
#define VC_ERR_INTERNAL 4
#define VC_ERR_INVALID_NAME 5
#define VC_ERR_MISSING_MANDATORY 6
// static
QString VCard::getError(int err) {
switch(err) {
case VC_ERR_INVALID_LINE:
return i18n("The vCard line was invalid.");
case VC_ERR_INTERNAL:
return i18n("An unknown internal error occurred. Please report to kmail@kde.org");
case VC_ERR_INVALID_NAME:
return i18n("The vCard contained an invalid field.");
case VC_ERR_MISSING_MANDATORY:
return i18n("The vCard is missing a mandatory field.");
case VC_ERR_NO_BEGIN:
return i18n("No beginning was found.");
case VC_ERR_NO_END:
return i18n("No end was found.");
case 0:
return i18n("No error.");
default:
return i18n("Invalid error number.");
}
}
// thses are BITS (1,2,4,8,16,...) !
#define VC_STATE_BEGIN 1
#define VC_STATE_BODY 2
#define VC_STATE_END 4
#define VC_STATE_HAVE_N 8
#define VC_STATE_HAVE_VERSION 16
//
// This class is not particularily optimized. This is by design.
// The code is rather complicated (much token finding/parsing) and
// it's much easier to understand in this form. It is still a
// good candidate for optimzation in the future though. (once the code
// has been proven and stabilized.)
//
// static
VCard *VCard::parseVCard(const QString& vc, int *err) {
int _err = 0;
int _state = VC_STATE_BEGIN;
QValueList<VCardLine> *_vcdata;
QValueList<QString> lines;
_vcdata = new QValueList<VCardLine>;
// lines = tokenizeBy(vc, '\n');
lines = tokenizeBy(vc, QRegExp("[\x0d\x0a]"));
// for each line in the vCard
for (QValueListIterator<QString> j = lines.begin();
j != lines.end(); ++j) {
VCardLine _vcl;
// take spaces off the end - ugly but necessary hack
for (int g = (*j).length()-1; g > 0 && (*j)[g].isSpace(); g++)
(*j)[g] = 0;
// first token:
// verify state, update if necessary
if (_state & VC_STATE_BEGIN) {
if (!qstricmp((*j).latin1(), VCARD_BEGIN)) {
_state = VC_STATE_BODY;
continue;
} else {
_err = VC_ERR_NO_BEGIN;
break;
}
} else if (_state & VC_STATE_BODY) {
if (!qstricmp((*j).latin1(), VCARD_END)) {
_state |= VC_STATE_END;
break;
}
// split into two tokens
// QValueList<QString> linetokens = tokenizeBy(*j, QRegExp(":"));
unsigned int tail = (*j).find(':', 0);
if (tail > (*j).length()) { // invalid line - no ':'
_err = VC_ERR_INVALID_LINE;
break;
}
QValueList<QString> linetokens;
QString tmplinetoken;
tmplinetoken = (*j);
tmplinetoken.truncate(tail);
linetokens.append(tmplinetoken);
tmplinetoken = (*j).mid(tail+1);
linetokens.append(tmplinetoken);
// check for qualifiers and
// set name, qualified, qualifier(s)
//QValueList<QString> nametokens = tokenizeBy(linetokens[0], ';');
QValueList<QString> nametokens = tokenizeBy(linetokens[0], QRegExp(";"));
bool qp = false, first_pass = true;
bool b64 = false;
if (nametokens.count() > 0) {
_vcl.qualified = false;
_vcl.name = nametokens[0];
_vcl.name = _vcl.name.lower();
for (QValueListIterator<QString> z = nametokens.begin();
z != nametokens.end();
++z) {
QString zz = (*z).lower();
if (zz == VCARD_QUOTED_PRINTABLE || zz == VCARD_ENCODING_QUOTED_PRINTABLE) {
qp = true;
} else if (zz == VCARD_BASE64) {
b64 = true;
} else if (!first_pass) {
_vcl.qualified = true;
_vcl.qualifiers.append(zz);
}
first_pass = false;
}
} else {
_err = VC_ERR_INVALID_LINE;
}
if (_err != 0) break;
if (_vcl.name == VCARD_VERSION)
_state |= VC_STATE_HAVE_VERSION;
if (_vcl.name == VCARD_N || _vcl.name == VCARD_FN)
_state |= VC_STATE_HAVE_N;
// second token:
// split into tokens by ;
// add to parameters vector
//_vcl.parameters = tokenizeBy(linetokens[1], ';');
if (b64) {
if (linetokens[1][linetokens[1].length()-1] != '=')
do {
linetokens[1] += *(++j);
} while ((*j)[(*j).length()-1] != '=');
} else {
if (qp) { // join any split lines
while (linetokens[1][linetokens[1].length()-1] == '=') {
linetokens[1].remove(linetokens[1].length()-1, 1);
linetokens[1].append(*(++j));
}
}
_vcl.parameters = tokenizeBy(linetokens[1], QRegExp(";"), true);
if (qp) { // decode the quoted printable
for (QValueListIterator<QString> z = _vcl.parameters.begin();
z != _vcl.parameters.end();
++z) {
_vcl.qpDecode(*z);
}
}
}
} else {
_err = VC_ERR_INTERNAL;
break;
}
// validate VCardLine
if (!_vcl.isValid()) {
_err = VC_ERR_INVALID_LINE;
break;
}
// add to vector
_vcdata->append(_vcl);
}
// errors to check at the last minute (exit state related)
if (_err == 0) {
if (!(_state & VC_STATE_END)) // we have to have an end!!
_err = VC_ERR_NO_END;
if (!(_state & VC_STATE_HAVE_N) || // we have to have the mandatories!
!(_state & VC_STATE_HAVE_VERSION))
_err = VC_ERR_MISSING_MANDATORY;
}
// set the error message if we can, and only return an object
// if the vCard was valid.
if (err)
*err = _err;
if (_err != 0) {
delete _vcdata;
return NULL;
}
return new VCard(_vcdata);
}
void VCard::clean() {
_vcdata->clear();
}
bool VCard::removeLine(const QString& name) {
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == name && !(*i).qualified) {
_vcdata->remove(i);
return true;
}
}
return false;
}
bool VCard::removeQualifiedLine(const QString& name, const QString& qualifier) {
const QString lowqualifier = qualifier.lower();
const QString lowname = name.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && (*i).qualifiers.contains(lowqualifier)) {
(*i).qualifiers.remove(lowqualifier);
if ((*i).qualifiers.isEmpty()) {
_vcdata->remove(i);
}
return true;
}
}
return false;
}
int VCard::addLine(const QString& name, const QString& value) {
QValueList<QString> values;
values.append(value);
return addLine(name.lower(), values);
}
// FIXME: We should see if we can just add a new qualifier first
// - involves testing name and value.
int VCard::addQualifiedLine(const QString& name, const QString& qualifier, const QString& value) {
QValueList<QString> values;
values.append(value);
return addQualifiedLine(name, qualifier, values);
}
int VCard::addLine(const QString& name, const QValueList<QString>& value) {
const QString lowname = name.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && !(*i).qualified) {
(*i).parameters = value;
if ((*i).isValid()) {
return 0;
}
_vcdata->remove(i);
return VC_ERR_INVALID_LINE;
}
}
VCardLine vcl;
vcl.name = lowname;
vcl.qualified = false;
vcl.parameters = value;
_vcdata->append(vcl);
return false;
}
int VCard::addQualifiedLine(const QString& name, const QString& qualifier, const QValueList<QString>& value) {
const QString lowname = name.lower();
const QString lowqualifier = qualifier.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && (*i).qualified && (*i).qualifiers.contains(lowqualifier)) {
if ((*i).parameters == value) return 0; // it's already there
if ((*i).qualifiers.count() > 1) { // there are others, remove and break
(*i).qualifiers.remove(lowqualifier);
break;
}
(*i).parameters = value;
if ((*i).isValid()) {
return 0;
}
_vcdata->remove(i);
return VC_ERR_INVALID_LINE;
}
}
VCardLine vcl;
vcl.name = lowname;
vcl.qualifiers.append(lowqualifier);
vcl.qualified = true;
vcl.parameters = value;
_vcdata->append(vcl);
return 0;
}
QString VCard::getValue(const QString& name, const QString& qualifier) {
QString failed = "";
const QString lowname = name.lower();
const QString lowqualifier = qualifier.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && (*i).qualified && (*i).qualifiers.contains(lowqualifier)) {
if ((*i).parameters.count() > 0)
return (*i).parameters[0];
else return failed;
}
}
return failed;
}
QString VCard::getValue(const QString& name) {
QString failed = "";
const QString lowname = name.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && !(*i).qualified) {
if ((*i).parameters.count() > 0)
return (*i).parameters[0];
else return failed;
}
}
return failed;
}
QValueList<QString> VCard::getValues(const QString& name, const QString& qualifier) {
//QString failedstr = "";
QValueList<QString> failed;
const QString lowname = name.lower();
const QString lowqualifier = qualifier.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && (*i).qualified && (*i).qualifiers.contains(lowqualifier)) {
return (*i).parameters;
}
}
//failed.append(failedstr);
return failed;
}
QValueList<QString> VCard::getValues(const QString& name) {
//QString failedstr = "";
QValueList<QString> failed;
const QString lowname = name.lower();
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
if ((*i).name == lowname && !(*i).qualified) {
return (*i).parameters;
}
}
//failed.append(failedstr);
return failed;
}
QString VCard::getVCard() const {
QString vct = VCARD_BEGIN;
int cnt, cur;
vct += "\n";
for (QValueListIterator<VCardLine> i = _vcdata->begin();
i != _vcdata->end();
++i) {
QString parms = "";
vct += (*i).name;
if ((*i).qualified) {
for (QValueListIterator<QString> k = (*i).qualifiers.begin();
k != (*i).qualifiers.end();
++k) {
vct += ";";
vct += *k;
}
}
cnt = (*i).parameters.count();
cur = 0;
for (QValueListIterator<QString> j = (*i).parameters.begin();
j != (*i).parameters.end();
++j) {
QString qpj = *j;
(*i).qpEncode(qpj);
if (qpj != *j) {
vct += ";";
vct += VCARD_QUOTED_PRINTABLE; // Netscape prints it multiple times.
} // I don't know if this is necessary.
parms += qpj;
if (++cur < cnt)
parms += ";";
}
vct += ":";
vct += parms;
vct += "\n";
}
vct += VCARD_END;
vct += "\n";
return vct;
}
//
// Characters 0x20->0x7E are considered "printable". The rest must be
// encoded. 0x3B, 0x3D are also special (not printable). Any others?
//
void VCardLine::qpEncode(QString& x) {
QString y = x;
int c;
x = "";
c = y.length();
for (int i = 0; i < c; i++) {
if (y[i].latin1() == 0x13 && y[i+1].latin1() != 0x10) {
x += "=0D=0A";
} else if (y[i].latin1() > 0x7e ||
y[i].latin1() < 0x20 ||
y[i].latin1() == 0x3B ||
y[i].latin1() == 0x3D ) {
QString z;
z.sprintf("=%.2X", y[i].latin1());
x += z;
} else {
x += y[i];
}
}
}
void VCardLine::qpDecode(QString& x) {
QString y = x;
int c;
x = "";
c = y.length();
for (int i = 0; i < c; i++) {
if (y[i] == '=') {
char p = y[++i].latin1();
char q = y[++i].latin1();
x += (char) ((p <= '9' ? p - '0': p - 'A' + 10)*16 +
(q <= '9' ? q - '0': q - 'A' + 10));
} else {
x += y[i];
}
}
}
// true if the line is a valid vcard line
bool VCardLine::isValid() const {
// invalid: if it is "begin:vcard" or "end:vcard"
if (name == VCARD_BEGIN_N || name == VCARD_END_N)
return false;
if (name[0] == 'x' && name[1] == '-') // a custom x- line
return true;
// this is long but it makes it a bit faster (and saves me from using
// a trie which is probably the ideal situation, but a bit memory heavy)
switch(name[0]) {
case 'a':
// GS - this seems to not be necessary? - netscape doesn't do it
// if (name == VCARD_ADR && qualified &&
// (qualifiers.contains(VCARD_ADR_DOM) ||
// qualifiers.contains(VCARD_ADR_INTL) ||
// qualifiers.contains(VCARD_ADR_POSTAL) ||
// qualifiers.contains(VCARD_ADR_HOME) ||
// qualifiers.contains(VCARD_ADR_WORK) ||
// qualifiers.contains(VCARD_ADR_PREF)
// ))
if (name == VCARD_ADR)
return true;
if (name == VCARD_AGENT)
return true;
break;
case 'b':
if (name == VCARD_BDAY)
return true;
break;
case 'c':
if (name == VCARD_CATEGORIES)
return true;
if (name == VCARD_CLASS && qualified &&
(qualifiers.contains(VCARD_CLASS_PUBLIC) ||
qualifiers.contains(VCARD_CLASS_PRIVATE) ||
qualifiers.contains(VCARD_CLASS_CONFIDENTIAL)
))
return true;
break;
case 'd':
break;
case 'e':
if (name == VCARD_EMAIL && qualified &&
(qualifiers.contains(VCARD_EMAIL_INTERNET) ||
qualifiers.contains(VCARD_EMAIL_PREF) ||
qualifiers.contains(VCARD_EMAIL_X400)
))
return true;
break;
case 'f':
if (name == VCARD_FN)
return true;
break;
case 'g':
if (name == VCARD_GEO)
return true;
break;
case 'h':
break;
case 'i':
break;
case 'j':
break;
case 'k':
if (name == VCARD_KEY && qualified &&
(qualifiers.contains(VCARD_KEY_X509) ||
qualifiers.contains(VCARD_KEY_PGP)
))
return true;
break;
case 'l':
if (name == VCARD_LABEL)
return true;
if (name == VCARD_LOGO)
return true;
break;
case 'm':
if (name == VCARD_MAILER)
return true;
break;
case 'n':
if (name == VCARD_N)
return true;
if (name == VCARD_NAME)
return true;
if (name == VCARD_NICKNAME)
return true;
if (name == VCARD_NOTE)
return true;
break;
case 'o':
if (name == VCARD_ORG)
return true;
break;
case 'p':
if (name == VCARD_PHOTO)
return true;
if (name == VCARD_PROFILE)
return true;
if (name == VCARD_PRODID)
return true;
break;
case 'q':
break;
case 'r':
if (name == VCARD_ROLE)
return true;
if (name == VCARD_REV)
return true;
break;
case 's':
if (name == VCARD_SOURCE)
return true;
if (name == VCARD_SOUND)
return true;
break;
case 't':
if (name == VCARD_TEL && qualified &&
(qualifiers.contains(VCARD_TEL_HOME) ||
qualifiers.contains(VCARD_TEL_WORK) ||
qualifiers.contains(VCARD_TEL_PREF) ||
qualifiers.contains(VCARD_TEL_VOICE) ||
qualifiers.contains(VCARD_TEL_FAX) ||
qualifiers.contains(VCARD_TEL_MSG) ||
qualifiers.contains(VCARD_TEL_CELL) ||
qualifiers.contains(VCARD_TEL_PAGER) ||
qualifiers.contains(VCARD_TEL_BBS) ||
qualifiers.contains(VCARD_TEL_MODEM) ||
qualifiers.contains(VCARD_TEL_CAR) ||
qualifiers.contains(VCARD_TEL_ISDN) ||
qualifiers.contains(VCARD_TEL_VIDEO) ||
qualifiers.contains(VCARD_TEL_PCS)
))
return true;
if (name == VCARD_TZ)
return true;
if (name == VCARD_TITLE)
return true;
break;
case 'u':
if (name == VCARD_URL)
return true;
if (name == VCARD_UID)
return true;
break;
case 'v':
if (name == VCARD_VERSION)
return true;
break;
case 'w':
break;
case 'x':
break;
case 'y':
break;
case 'z':
break;
default:
break;
}
return false;
}