3 /// Blackberry database record parser class for contact records.
7 Copyright (C) 2005-2011, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "r_contact.h"
23 #include "record-internal.h"
25 #include "protostructs.h"
36 #define __DEBUG_MODE__
40 using namespace Barry::Protocol
;
46 ///////////////////////////////////////////////////////////////////////////////
49 // Contact field codes
53 #define CFC_WORK_PHONE 6
54 #define CFC_HOME_PHONE 7
55 #define CFC_MOBILE_PHONE 8
58 #define CFC_RADIO 14 // 0x0e
59 #define CFC_WORK_PHONE_2 16 // 0x10
60 #define CFC_HOME_PHONE_2 17 // 0x11
61 #define CFC_OTHER_PHONE 18 // 0x12
62 #define CFC_MOBILE_PHONE_2 19 // 0x13
63 #define CFC_HOME_FAX 20 // 0x14
64 #define CFC_NAME 32 // 0x20 used twice, in first/last name order
65 #define CFC_COMPANY 33
66 #define CFC_DEFAULT_COMM_METHOD 34
67 #define CFC_ADDRESS1 35
68 #define CFC_ADDRESS2 36
69 #define CFC_ADDRESS3 37
71 #define CFC_PROVINCE 39
72 #define CFC_POSTAL_CODE 40
73 #define CFC_COUNTRY 41
74 #define CFC_TITLE 42 // 0x2a
75 #define CFC_PUBLIC_KEY 43
76 #define CFC_GROUP_FLAG 44
77 #define CFC_GROUP_LINK 52
78 #define CFC_URL 54 // 0x36
79 #define CFC_PREFIX 55 // 0x37
80 #define CFC_CATEGORY 59 // 0x3B
81 #define CFC_HOME_ADDRESS1 61 // 0x3D
82 #define CFC_HOME_ADDRESS2 62 // 0x3E
83 // If the address 3 isn't mapped then it appears
84 // in the same field as address2 with a space
85 #define CFC_HOME_ADDRESS3 63 // 0x3F
86 #define CFC_NOTES 64 // 0x40
87 #define CFC_USER_DEFINED_1 65 // 0x41
88 #define CFC_USER_DEFINED_2 66 // 0x42
89 #define CFC_USER_DEFINED_3 67 // 0x43
90 #define CFC_USER_DEFINED_4 68 // 0x44
91 #define CFC_HOME_CITY 69 // 0x45
92 #define CFC_HOME_PROVINCE 70 // 0x46
93 #define CFC_HOME_POSTAL_CODE 71 // 0x47
94 #define CFC_HOME_COUNTRY 72 // 0x48
95 #define CFC_IMAGE 77 // 0x4D
96 #define CFC_BIRTHDAY 82 // 0x52
97 #define CFC_ANNIVERSARY 83 // 0x53
98 #define CFC_MAYBE_CATEGORYID 84 // 0x54
99 #define CFC_UNIQUEID 85 // 0x55
100 #define CFC_NICKNAME 86 // 0x56
101 #define CFC_INVALID_FIELD 255
103 // Contact code to field table
104 static FieldLink
<Contact
> ContactFieldLinks
[] = {
105 { CFC_NICKNAME
, "Nickname", 0,0, &Contact::Nickname
, 0, 0, 0, 0, true },
106 { CFC_PHONE
, "Phone", 0,0, &Contact::Phone
, 0, 0, 0, 0, true },
107 { CFC_FAX
, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax
, 0, 0, 0, 0, true },
108 { CFC_HOME_FAX
, "HomeFax", 0,0, &Contact::HomeFax
, 0, 0, 0, 0, true },
109 { CFC_WORK_PHONE
, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone
, 0, 0, 0, 0, true },
110 { CFC_HOME_PHONE
, "HomePhone", "homePhone",0, &Contact::HomePhone
, 0, 0, 0, 0, true },
111 { CFC_MOBILE_PHONE
, "MobilePhone","mobile",0, &Contact::MobilePhone
, 0, 0, 0, 0, true },
112 { CFC_MOBILE_PHONE_2
,"MobilePhone2",0,0, &Contact::MobilePhone2
, 0, 0, 0, 0, true },
113 { CFC_PAGER
, "Pager", "pager",0, &Contact::Pager
, 0, 0, 0, 0, true },
114 { CFC_PIN
, "PIN", 0,0, &Contact::PIN
, 0, 0, 0, 0, true },
115 { CFC_RADIO
, "Radio", 0,0, &Contact::Radio
, 0, 0, 0, 0, true },
116 { CFC_WORK_PHONE_2
, "WorkPhone2", 0,0, &Contact::WorkPhone2
, 0, 0, 0, 0, true },
117 { CFC_HOME_PHONE_2
, "HomePhone2", 0,0, &Contact::HomePhone2
, 0, 0, 0, 0, true },
118 { CFC_OTHER_PHONE
, "OtherPhone", 0,0, &Contact::OtherPhone
, 0, 0, 0, 0, true },
119 { CFC_COMPANY
, "Company", "o",0, &Contact::Company
, 0, 0, 0, 0, true },
120 { CFC_DEFAULT_COMM_METHOD
,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod
, 0, 0, 0, 0, true },
121 { CFC_ADDRESS1
, "WorkAddress1", 0,0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::Address1
, true },
122 { CFC_ADDRESS2
, "WorkAddress2", 0,0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::Address2
, true },
123 { CFC_ADDRESS3
, "WorkAddress3", 0,0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::Address3
, true },
124 { CFC_CITY
, "WorkCity", "l",0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::City
, true },
125 { CFC_PROVINCE
, "WorkProvince", "st",0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::Province
, true },
126 { CFC_POSTAL_CODE
, "WorkPostalCode", "postalCode",0, 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::PostalCode
, true },
127 { CFC_COUNTRY
, "WorkCountry", "c", "country", 0, 0, 0, &Contact::WorkAddress
, &PostalAddress::Country
, true },
128 { CFC_TITLE
, "JobTitle", "title",0, &Contact::JobTitle
, 0, 0, 0, 0, true },
129 { CFC_PUBLIC_KEY
, "PublicKey", 0,0, &Contact::PublicKey
, 0, 0, 0, 0, false },
130 { CFC_URL
, "URL", 0,0, &Contact::URL
, 0, 0, 0, 0, true },
131 { CFC_PREFIX
, "Prefix", 0,0, &Contact::Prefix
, 0, 0, 0, 0, true },
132 { CFC_HOME_ADDRESS1
,"HomeAddress1", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::Address1
, true },
133 { CFC_HOME_ADDRESS2
,"HomeAddress2", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::Address2
, true },
134 { CFC_HOME_ADDRESS3
,"HomeAddress3", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::Address3
, true },
135 { CFC_NOTES
, "Notes", 0,0, &Contact::Notes
, 0, 0, 0, 0, true },
136 { CFC_USER_DEFINED_1
, "UserDefined1", 0,0, &Contact::UserDefined1
, 0, 0, 0, 0, true },
137 { CFC_USER_DEFINED_2
, "UserDefined2", 0,0, &Contact::UserDefined2
, 0, 0, 0, 0, true },
138 { CFC_USER_DEFINED_3
, "UserDefined3", 0,0, &Contact::UserDefined3
, 0, 0, 0, 0, true },
139 { CFC_USER_DEFINED_4
, "UserDefined4", 0,0, &Contact::UserDefined4
, 0, 0, 0, 0, true },
140 { CFC_HOME_CITY
, "HomeCity", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::City
, true },
141 { CFC_HOME_PROVINCE
,"HomeProvince", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::Province
, true },
142 { CFC_HOME_POSTAL_CODE
, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::PostalCode
, true },
143 { CFC_HOME_COUNTRY
, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress
, &PostalAddress::Country
, true },
144 { CFC_IMAGE
, "Image", 0,0, &Contact::Image
, 0, 0, 0, 0, false },
145 { CFC_INVALID_FIELD
,"EndOfList", 0, 0, 0, 0, 0, 0, 0, false }
149 : RecType(Contact::GetDefaultRecType()),
151 m_FirstNameSeen(false)
159 const unsigned char* Contact::ParseField(const unsigned char *begin
,
160 const unsigned char *end
,
161 const IConverter
*ic
)
163 const CommonField
*field
= (const CommonField
*) begin
;
165 // advance and check size
166 begin
+= COMMON_FIELD_HEADER_SIZE
+ btohs(field
->size
);
167 if( begin
> end
) // if begin==end, we are ok
170 if( !btohs(field
->size
) ) // if field has no size, something's up
173 // cycle through the type table
174 for( FieldLink
<Contact
> *b
= ContactFieldLinks
;
175 b
->type
!= CFC_INVALID_FIELD
;
178 if( b
->type
== field
->type
) {
180 std::string
&s
= this->*(b
->strMember
);
181 s
= ParseFieldString(field
);
182 if( b
->iconvNeeded
&& ic
)
184 return begin
; // done!
186 else if( b
->postMember
&& b
->postField
) {
187 std::string
&s
= (this->*(b
->postMember
)).*(b
->postField
);
188 s
= ParseFieldString(field
);
189 if( b
->iconvNeeded
&& ic
)
194 break; // fall through to special handling
199 // if not found in the type table, check for special handling
200 switch( field
->type
)
203 std::string s
= ParseFieldString(field
);
206 EmailAddresses
.push_back( s
);
211 // can be used multiple times, for first/last names
213 if( FirstName
.size() || m_FirstNameSeen
) {
214 // first name already filled, use last name
216 m_FirstNameSeen
= false;
220 m_FirstNameSeen
= true;
223 *name
= ParseFieldString(field
);
225 *name
= ic
->FromBB(*name
);
230 // just add the unique ID to the list
231 GroupLinks
.push_back(
232 GroupLink(field
->u
.link
.uniqueId
,
233 field
->u
.link
.unknown
));
237 // ignore the group flag... the presense of group link items
238 // behaves as the flag in this class
242 std::string catstring
= ParseFieldString(field
);
244 catstring
= ic
->FromBB(catstring
);
245 Categories
.CategoryStr2List(catstring
);
250 std::string bstring
= ParseFieldString(field
);
251 Birthday
.FromBBString(bstring
);
255 case CFC_ANNIVERSARY
: {
256 std::string astring
= ParseFieldString(field
);
257 Anniversary
.FromBBString(astring
);
262 // this is a duplicate of the UniqueID that comes from
263 // the envelope part of the protocol... just throw this
264 // away, since when we upload it, we need to use a
265 // consisten UniqueID / RecordID from the API
269 // if still not handled, add to the Unknowns list
271 uf
.type
= field
->type
;
272 uf
.data
.assign((const char*)field
->u
.raw
, btohs(field
->size
));
273 Unknowns
.push_back(uf
);
275 // return new pointer for next field
279 void Contact::ParseHeader(const Data
&data
, size_t &offset
)
281 // no header to parse in Contact records
284 void Contact::ParseFields(const Data
&data
, size_t &offset
, const IConverter
*ic
)
286 const unsigned char *finish
= ParseCommonFields(*this,
287 data
.GetData() + offset
, data
.GetData() + data
.GetSize(), ic
);
288 offset
+= finish
- (data
.GetData() + offset
);
291 void Contact::BuildHeader(Data
&data
, size_t &offset
) const
293 // no header in Contact records
299 /// Build fields part of record
301 void Contact::BuildFields(Data
&data
, size_t &offset
, const IConverter
*ic
) const
305 // Sanity check: the Blackberry requires at least a name or
306 // a company name for each address record.
307 if( !GetFullName().size() && !Company
.size() )
308 throw BadData("Contact must have name or company name.");
310 // check if this is a group link record, and if so, output
312 if( GroupLinks
.size() )
313 BuildField(data
, offset
, CFC_GROUP_FLAG
, 'G');
315 // special fields not in type table
316 if( FirstName
.size() ) {
317 std::string s
= ic
? ic
->ToBB(FirstName
) : FirstName
;
318 BuildField(data
, offset
, CFC_NAME
, s
);
320 if( LastName
.size() ) {
321 if( !FirstName
.size() ) {
322 // order matters with first/last name, and if
323 // last name exists, and first name doesn't,
324 // insert blank first name ahead of it
325 BuildField(data
, offset
, CFC_NAME
, "");
327 BuildField(data
, offset
, CFC_NAME
, ic
? ic
->ToBB(LastName
) : LastName
);
331 // // add unknown data
332 // char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
333 // BuildField(data, offset, 0x54, buffer, 8);
335 // With the BlackBerry Storm, I have to add this entry.
336 // Otherwise the uniqueId of this contact is reseted !
337 // The device seems accept the multiple contact with the same uniqueId,
338 // but the synchronization process uses this uniqueId to identify the contact.
340 BuildField(data
, offset
, CFC_UNIQUEID
, RecordId
);
342 // add all email addresses
343 EmailList::const_iterator eai
= EmailAddresses
.begin();
344 for( ; eai
!= EmailAddresses
.end(); ++eai
) {
346 BuildField(data
, offset
, CFC_EMAIL
, ic
? ic
->ToBB(*eai
) : *eai
);
350 // cycle through the type table
351 for( FieldLink
<Contact
> *b
= ContactFieldLinks
;
352 b
->type
!= CFC_INVALID_FIELD
;
355 // print only fields with data
357 const std::string
&field
= this->*(b
->strMember
);
359 std::string s
= (b
->iconvNeeded
&& ic
) ? ic
->ToBB(field
) : field
;
360 BuildField(data
, offset
, b
->type
, s
);
363 else if( b
->postMember
&& b
->postField
) {
364 const std::string
&field
= (this->*(b
->postMember
)).*(b
->postField
);
366 std::string s
= (b
->iconvNeeded
&& ic
) ? ic
->ToBB(field
) : field
;
367 BuildField(data
, offset
, b
->type
, s
);
372 // save any group links
373 GroupLinksType::const_iterator
374 gb
= GroupLinks
.begin(), ge
= GroupLinks
.end();
375 for( ; gb
!= ge
; gb
++ ) {
376 Barry::Protocol::GroupLink link
;
377 link
.uniqueId
= htobl(gb
->Link
);
378 link
.unknown
= htobs(gb
->Unknown
);
379 BuildField(data
, offset
, CFC_GROUP_LINK
, link
);
383 if( Categories
.size() ) {
385 Categories
.CategoryList2Str(store
);
386 BuildField(data
, offset
, CFC_CATEGORY
, ic
? ic
->ToBB(store
) : store
);
389 // save Birthday and Anniversary
390 if( Birthday
.HasData() )
391 BuildField(data
, offset
, CFC_BIRTHDAY
, Birthday
.ToBBString());
392 if( Anniversary
.HasData() )
393 BuildField(data
, offset
, CFC_ANNIVERSARY
, Anniversary
.ToBBString());
395 // and finally save unknowns
396 UnknownsType::const_iterator
397 ub
= Unknowns
.begin(), ue
= Unknowns
.end();
398 for( ; ub
!= ue
; ub
++ ) {
399 BuildField(data
, offset
, *ub
);
402 data
.ReleaseBuffer(offset
);
405 void Contact::Clear()
407 RecType
= GetDefaultRecType();
410 EmailAddresses
.clear();
418 MobilePhone2
.clear();
428 DefaultCommunicationsMethod
.clear();
434 UserDefined1
.clear();
435 UserDefined2
.clear();
436 UserDefined3
.clear();
437 UserDefined4
.clear();
452 m_FirstNameSeen
= false;
455 std::string
Contact::GetDescription() const
457 return GetFullName();
463 /// Helper function that returns a formatted full name
465 std::string
Contact::GetFullName() const
467 std::string Full
= FirstName
;
468 if( Full
.size() && LastName
.size() )
477 /// Helper function that always returns a valid string. The string
478 /// may be empty if there is no address at the specified index.
480 const std::string
& Contact::GetEmail(unsigned int index
) const
482 static const std::string blank
;
483 if( index
< EmailAddresses
.size() )
484 return EmailAddresses
[index
];
488 void Contact::Dump(std::ostream
&os
) const
490 ios::fmtflags oldflags
= os
.setf(ios::left
);
491 char fill
= os
.fill(' ');
493 os
<< "Contact: 0x" << setbase(16) << GetID()
494 << " (" << (unsigned int)RecType
<< ")\n";
496 // special fields not in type table
497 os
<< " " << setw(20) << "FirstName";
498 os
<< ": " << FirstName
<< "\n";
499 os
<< " " << setw(20) << "LastName";
500 os
<< ": " << LastName
<< "\n";
502 // cycle through email addresses
503 EmailList::const_iterator eai
= EmailAddresses
.begin();
504 for( ; eai
!= EmailAddresses
.end(); ++eai
) {
506 os
<< " Email : " << *eai
<< "\n";
510 // cycle through the type table
511 for( FieldLink
<Contact
> *b
= ContactFieldLinks
;
512 b
->type
!= CFC_INVALID_FIELD
;
515 // special case: don't dump the raw image data, but
516 // leave that for a special hex dump
517 if( b
->type
== CFC_IMAGE
)
520 const std::string
*pField
= 0;
522 pField
= &(this->*(b
->strMember
));
524 else if( b
->postMember
&& b
->postField
) {
525 pField
= &((this->*(b
->postMember
)).*(b
->postField
));
528 // print only fields with data
529 if( pField
&& pField
->size() ) {
530 os
<< " " << setw(20) << b
->name
;
531 os
<< ": " << Cr2LfWrapper(*pField
) << "\n";
535 if( Categories
.size() ) {
537 Categories
.CategoryList2Str(display
);
538 os
<< " Categories : " << display
<< "\n";
541 // print Birthday and Anniversary
542 if( Birthday
.HasData() ) {
543 os
<< " Birthday : " << Birthday
<< "\n";
545 if( Anniversary
.HasData() ) {
546 os
<< " Anniversary : " << Anniversary
<< "\n";
549 // print any group links
550 GroupLinksType::const_iterator
551 gb
= GroupLinks
.begin(), ge
= GroupLinks
.end();
553 os
<< " GroupLinks:\n";
554 for( ; gb
!= ge
; gb
++ ) {
555 os
<< " ID: 0x" << setbase(16) << gb
->Link
<< "\n";
558 // print Image in hex dump format, if available
560 Data
image(Image
.data(), Image
.size());
561 os
<< " Photo image:\n";
565 // and finally print unknowns
568 // cleanup the stream
573 bool Contact::operator<(const Contact
&other
) const
575 // old sorting mechanism, to put group links at the bottom
576 //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
577 // testing - put group links at the top
578 //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
580 // usually one of these fields is filled in, so compare
581 // them all in a ( LastName + FirstName + Company ) key style
582 int cmp
= LastName
.compare(other
.LastName
);
584 cmp
= FirstName
.compare(other
.FirstName
);
586 cmp
= Company
.compare(other
.Company
);
590 void Contact::SplitName(const std::string
&full
, std::string
&first
, std::string
&last
)
595 string::size_type pos
= full
.find_last_of(' ');
596 if( pos
!= string::npos
) {
597 // has space, assume last word is last name
598 last
= full
.c_str() + pos
+ 1;
599 first
= full
.substr(0, pos
);
602 // no space, assume only first name
603 first
= full
.substr(0);