486 lines
15 KiB
C++
486 lines
15 KiB
C++
/**
|
|
* Copyright (C) 2003-2006 Funambol
|
|
*
|
|
* 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., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "posixadapter.h"
|
|
|
|
#include "base/util/utils.h"
|
|
#include "base/util/WString.h"
|
|
#include "base/Log.h"
|
|
#include "VObject.h"
|
|
|
|
namespace vocl {
|
|
|
|
VObject::VObject() {
|
|
productID = NULL;
|
|
version = NULL;
|
|
properties = new ArrayList();
|
|
}
|
|
|
|
VObject::~VObject() {
|
|
if (productID) {
|
|
delete [] productID; productID = NULL;
|
|
}
|
|
if (version) {
|
|
delete [] version; version = NULL;
|
|
}
|
|
if (properties) {
|
|
delete properties; properties = NULL;
|
|
}
|
|
}
|
|
|
|
void VObject::set(wchar_t** p, wchar_t* v) {
|
|
if (*p) {
|
|
delete [] *p;
|
|
}
|
|
*p = (v) ? wstrdup(v) : NULL;
|
|
}
|
|
|
|
void VObject::setVersion(wchar_t* ver) {
|
|
set(&version, ver);
|
|
}
|
|
|
|
void VObject::setProdID(wchar_t* prodID) {
|
|
set(&productID, prodID);
|
|
}
|
|
|
|
wchar_t* VObject::getVersion() {
|
|
return version;
|
|
}
|
|
|
|
wchar_t* VObject::getProdID() {
|
|
return productID;
|
|
}
|
|
|
|
void VObject::addProperty(VProperty* vProp) {
|
|
properties->add((ArrayElement&) *vProp);
|
|
}
|
|
|
|
int VObject::propertiesCount() {
|
|
return properties->size();
|
|
}
|
|
|
|
bool VObject::removeProperty(int index) {
|
|
if(index < 0 || index >= propertiesCount())
|
|
return false;
|
|
properties->remove(index);
|
|
return true;
|
|
}
|
|
|
|
void VObject::removeProperty(wchar_t* propName) {
|
|
for (int i=0; i<properties->size(); i++) {
|
|
VProperty *property;
|
|
property = (VProperty* )properties->get(i);
|
|
if(!wcscmp(property->getName(), propName)) {
|
|
properties->remove(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VObject::containsProperty(wchar_t* propName) {
|
|
for (int i=0; i<properties->size(); i++) {
|
|
VProperty *property;
|
|
property = (VProperty* )properties->get(i);
|
|
if(!wcscmp(property->getName(), propName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
VProperty* VObject::getProperty(int index) {
|
|
return (VProperty*)properties->get(index);
|
|
}
|
|
|
|
VProperty* VObject::getProperty(wchar_t* propName) {
|
|
for (int i=0; i<properties->size(); i++) {
|
|
|
|
VProperty *property;
|
|
property = (VProperty* )properties->get(i);
|
|
|
|
if(!wcscmp(property->getName(), propName)) {
|
|
return property;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
wchar_t* VObject::toString() {
|
|
|
|
WString strVObject;
|
|
const wchar_t* eof;
|
|
|
|
// vcard 2.1 and 3.0 both use \r\n as line ending
|
|
eof = TEXT("\r\n");
|
|
|
|
for (int i=0; i<properties->size(); i++) {
|
|
VProperty *property;
|
|
property = (VProperty*)properties->get(i);
|
|
if(property->containsParameter(TEXT("GROUP"))) {
|
|
strVObject.append(property->getParameterValue(TEXT("GROUP")));
|
|
strVObject.append(TEXT("."));
|
|
property->removeParameter(TEXT("GROUP"));
|
|
}
|
|
strVObject.append(property->getName());
|
|
|
|
for(int k=0; k<property->parameterCount(); k++) {
|
|
strVObject.append(TEXT(";"));
|
|
|
|
wchar_t* paramName = new wchar_t[wcslen(property->getParameter(k))+1];
|
|
wcscpy(paramName, property->getParameter(k));
|
|
|
|
strVObject.append(paramName);
|
|
const wchar_t *value = property->getParameterValue(k);
|
|
if(value) {
|
|
strVObject.append(TEXT("="));
|
|
strVObject.append(value);
|
|
}
|
|
delete [] paramName; paramName = NULL;
|
|
}
|
|
|
|
strVObject.append(TEXT(":"));
|
|
if(property->getValue()) {
|
|
if(property->equalsEncoding(TEXT("BASE64"))) {
|
|
wchar_t delim[] = TEXT("\r\n ");
|
|
int fold = 76;
|
|
int sizeOfValue = int(wcslen(property->getValue()));
|
|
int size = sizeOfValue + (int)(sizeOfValue/fold + 2)*int(wcslen(delim));
|
|
int index = 0;
|
|
|
|
wchar_t* output = new wchar_t[size + 1];
|
|
wcscpy(output, TEXT("\0"));
|
|
|
|
while (index<sizeOfValue)
|
|
{
|
|
wcscat(output,delim);
|
|
wcsncat(output,property->getValue()+index,fold);
|
|
index+=fold;
|
|
}
|
|
|
|
strVObject.append(output);
|
|
// the extra empty line is needed because the Bachus-Naur
|
|
// specification of vCard 2.1 says so
|
|
strVObject.append(eof);
|
|
delete [] output;
|
|
}
|
|
else
|
|
strVObject.append(property->getValue());
|
|
}
|
|
strVObject.append(eof);
|
|
}
|
|
|
|
// memory must be free by caller with delete []
|
|
wchar_t *str = new wchar_t[strVObject.length() + 1];
|
|
wcscpy(str, strVObject.c_str());
|
|
return str;
|
|
}
|
|
|
|
void VObject::insertProperty(VProperty* property) {
|
|
|
|
if (propertiesCount() == 0 || wcscmp(getProperty(propertiesCount()-1)->getName(),TEXT("END")))
|
|
addProperty(property);
|
|
else {
|
|
VProperty* lastProperty = getProperty(TEXT("END"));
|
|
removeProperty(TEXT("END"));
|
|
addProperty(property);
|
|
addProperty(lastProperty);
|
|
}
|
|
}
|
|
|
|
void VObject::addFirstProperty(VProperty* property) {
|
|
properties->add(0,(ArrayElement&)*property);
|
|
}
|
|
|
|
void VObject::removeAllProperies(wchar_t* propName) {
|
|
for(int i = 0, m = propertiesCount(); i < m ; i++)
|
|
if(!wcscmp(getProperty(i)->getName(), propName)) {
|
|
removeProperty(i);
|
|
--i;
|
|
--m;
|
|
}
|
|
}
|
|
|
|
|
|
// Patrick Ohly: hack below, see header file
|
|
|
|
static int hex2int( wchar_t x )
|
|
{
|
|
return (x >= '0' && x <= '9') ? x - '0' :
|
|
(x >= 'A' && x <= 'F') ? x - 'A' + 10 :
|
|
(x >= 'a' && x <= 'f') ? x - 'a' + 10 :
|
|
0;
|
|
}
|
|
|
|
#define SEMICOLON_REPLACEMENT '\a'
|
|
|
|
void VObject::toNativeEncoding()
|
|
{
|
|
bool is_30 = !wcscmp(getVersion(), TEXT("3.0"));
|
|
bool is_21 = !wcscmp(getVersion(), TEXT("2.1"));
|
|
// line break is encoded with either one or two
|
|
// characters on different platforms
|
|
const int linebreaklen = wcslen(SYNC4J_LINEBREAK);
|
|
|
|
for (int index = propertiesCount() - 1; index >= 0; index--) {
|
|
VProperty *vprop = getProperty(index);
|
|
wchar_t *name = vprop->getName();
|
|
wchar_t *foreign = vprop->getValue();
|
|
// the native encoding is always shorter than the foreign one
|
|
wchar_t *native = new wchar_t[wcslen(foreign) + 1];
|
|
|
|
if (vprop->equalsEncoding(TEXT("QUOTED-PRINTABLE"))) {
|
|
int in = 0, out = 0;
|
|
wchar_t curr;
|
|
|
|
// this is a very crude quoted-printable decoder,
|
|
// based on Wikipedia's explanation of quoted-printable
|
|
while ((curr = foreign[in]) != 0) {
|
|
in++;
|
|
if (curr == '=') {
|
|
wchar_t values[2];
|
|
values[0] = foreign[in];
|
|
in++;
|
|
if (!values[0]) {
|
|
// incomplete?!
|
|
break;
|
|
}
|
|
values[1] = foreign[in];
|
|
in++;
|
|
if (values[0] == '\r' && values[1] == '\n') {
|
|
// soft line break, ignore it
|
|
} else {
|
|
native[out] = (hex2int(values[0]) << 4) |
|
|
hex2int(values[1]);
|
|
out++;
|
|
|
|
// replace \r\n with \n?
|
|
if ( linebreaklen == 1 &&
|
|
out >= 2 &&
|
|
native[out - 2] == '\r' &&
|
|
native[out - 1] == '\n' ) {
|
|
native[out - 2] = SYNC4J_LINEBREAK[0];
|
|
out--;
|
|
}
|
|
|
|
// the conversion to wchar on Windows is
|
|
// probably missing here
|
|
}
|
|
} else {
|
|
native[out] = curr;
|
|
out++;
|
|
}
|
|
}
|
|
native[out] = 0;
|
|
out++;
|
|
} else {
|
|
wcscpy(native, foreign);
|
|
}
|
|
|
|
// decode escaped characters after backslash:
|
|
// \n is line break only in 3.0
|
|
wchar_t curr;
|
|
int in = 0, out = 0;
|
|
while ((curr = native[in]) != 0) {
|
|
in++;
|
|
switch (curr) {
|
|
case '\\':
|
|
curr = native[in];
|
|
in++;
|
|
switch (curr) {
|
|
case 'n':
|
|
if (is_30) {
|
|
// replace with line break
|
|
wcsncpy(native + out, SYNC4J_LINEBREAK, linebreaklen);
|
|
out += linebreaklen;
|
|
} else {
|
|
// normal escaped character
|
|
native[out] = curr;
|
|
out++;
|
|
}
|
|
break;
|
|
case 0:
|
|
// unexpected end of string
|
|
break;
|
|
default:
|
|
// just copy next character
|
|
native[out] = curr;
|
|
out++;
|
|
break;
|
|
}
|
|
break;
|
|
case ';':
|
|
// Might be field separator, but beware:
|
|
// in vCard 2.1 a single, unescaped semicolon is valid in all
|
|
// properties which are single values and not structured.
|
|
// Some encoders even do that in 3.0, so always accept a literal
|
|
// ; as it is in properties which are not multi-value.
|
|
if (!wcsicmp(name, "N") ||
|
|
!wcsicmp(name, "ADR") ||
|
|
!wcsicmp(name, "ORG")) {
|
|
// must replace with something special
|
|
// so that we can encode it again in fromNativeEncoding()
|
|
native[out] = SEMICOLON_REPLACEMENT;
|
|
out++;
|
|
} else {
|
|
// copy literally
|
|
native[out] = ';';
|
|
out++;
|
|
}
|
|
break;
|
|
default:
|
|
native[out] = curr;
|
|
out++;
|
|
}
|
|
}
|
|
native[out] = 0;
|
|
out++;
|
|
|
|
// charset handling:
|
|
// - doesn't exist at the moment, vCards have to be in ASCII or UTF-8
|
|
// - an explicit CHARSET parameter is removed because its parameter
|
|
// value might differ between 2.1 and 3.0 (quotation marks allowed in
|
|
// 3.0 but not 2.1) and thus would require extra code to convert it;
|
|
// when charsets really get supported this needs to be addressed
|
|
wchar_t *charset = vprop->getParameterValue(TEXT("CHARSET"));
|
|
if (charset) {
|
|
// proper decoding of the value and the property value text
|
|
// would go here, for the time being we just remove the
|
|
// value
|
|
if (_wcsicmp(charset, TEXT("UTF-8")) &&
|
|
_wcsicmp(charset, TEXT("\"UTF-8\""))) {
|
|
LOG.error("ignoring unsupported charset");
|
|
}
|
|
vprop->removeParameter(TEXT("CHARSET"));
|
|
}
|
|
|
|
vprop->setValue(native);
|
|
delete [] native;
|
|
}
|
|
}
|
|
|
|
void VObject::fromNativeEncoding()
|
|
{
|
|
bool is_30 = !wcscmp(getVersion(), TEXT("3.0"));
|
|
|
|
for (int index = propertiesCount() - 1; index >= 0; index--) {
|
|
VProperty *vprop = getProperty(index);
|
|
|
|
wchar_t *native = vprop->getValue();
|
|
// in the worst case every comma/linebreak is replaced with
|
|
// two characters and each \n with =0D=0A
|
|
wchar_t *foreign = new wchar_t[6 * wcslen(native) + 1];
|
|
wchar_t curr;
|
|
int in = 0, out = 0;
|
|
// line break is encoded with either one or two
|
|
// characters on different platforms
|
|
const int linebreaklen = wcslen(SYNC4J_LINEBREAK);
|
|
|
|
// use backslash for special characters,
|
|
// if necessary do quoted-printable encoding
|
|
bool doquoted = !is_30 &&
|
|
wcsstr(native, SYNC4J_LINEBREAK) != NULL;
|
|
|
|
if (vprop->equalsEncoding(TEXT("QUOTED-PRINTABLE"))) {
|
|
// remove it, recreate if doing 2.1
|
|
vprop->removeParameter(TEXT("ENCODING"));
|
|
if (!is_30) {
|
|
doquoted = true;
|
|
}
|
|
}
|
|
|
|
// non-ASCII character encountered
|
|
bool utf8 = false;
|
|
|
|
while ((curr = native[in]) != 0) {
|
|
in++;
|
|
switch (curr) {
|
|
case ',':
|
|
if (!is_30) {
|
|
// normal character
|
|
foreign[out] = curr;
|
|
out++;
|
|
break;
|
|
}
|
|
// no break!
|
|
case ';':
|
|
case '\\':
|
|
foreign[out] = '\\';
|
|
out++;
|
|
foreign[out] = curr;
|
|
out++;
|
|
break;
|
|
case SEMICOLON_REPLACEMENT:
|
|
foreign[out] = ';';
|
|
out++;
|
|
break;
|
|
default:
|
|
bool currIsUTF8 = (unsigned char)curr >= 128;
|
|
|
|
if (currIsUTF8) {
|
|
utf8 = true;
|
|
if (!is_30 && !doquoted) {
|
|
// vCard 2.1 defaults to 7-bit; instead of
|
|
// overriding that we fall back to quoted-printable
|
|
doquoted = true;
|
|
}
|
|
}
|
|
|
|
if (doquoted &&
|
|
(curr == '=' || currIsUTF8)) {
|
|
// escape = and non-ASCII characters
|
|
wsprintf(foreign + out, TEXT("=%02X"), (unsigned int)(unsigned char)curr);
|
|
out += 3;
|
|
} else if (!wcsncmp(native + in - 1,
|
|
SYNC4J_LINEBREAK,
|
|
linebreaklen)) {
|
|
// line break
|
|
if (is_30) {
|
|
foreign[out] = '\\';
|
|
out++;
|
|
foreign[out] = 'n';
|
|
out++;
|
|
} else {
|
|
wcscpy(foreign + out, TEXT("=0D=0A"));
|
|
out += 6;
|
|
}
|
|
in += linebreaklen - 1;
|
|
} else {
|
|
foreign[out] = curr;
|
|
out++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
foreign[out] = 0;
|
|
vprop->setValue(foreign);
|
|
delete [] foreign;
|
|
if (doquoted) {
|
|
// we have used quoted-printable encoding
|
|
vprop->addParameter(TEXT("ENCODING"), TEXT("QUOTED-PRINTABLE"));
|
|
}
|
|
if (utf8 &&
|
|
!is_30 &&
|
|
!vprop->getParameterValue("CHARSET")) {
|
|
vprop->addParameter("CHARSET", "UTF-8");
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|