Added clarifying comment to CodFileBuilder
[barry.git] / src / r_contact.cc
blobc31b2a904df7bcf4e21c414dc1dcd57a209b2423
1 ///
2 /// \file r_contact.cc
3 /// Blackberry database record parser class for contact records.
4 ///
6 /*
7 Copyright (C) 2005-2009, 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"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "data.h"
27 #include "time.h"
28 #include "error.h"
29 #include "endian.h"
30 #include "iconv.h"
31 #include <ostream>
32 #include <iomanip>
33 #include <time.h>
34 #include <stdexcept>
36 #define __DEBUG_MODE__
37 #include "debug.h"
39 using namespace std;
40 using namespace Barry::Protocol;
42 namespace Barry {
46 ///////////////////////////////////////////////////////////////////////////////
47 // Contact class
49 // Contact field codes
50 #define CFC_EMAIL 1
51 #define CFC_PHONE 2
52 #define CFC_FAX 3
53 #define CFC_WORK_PHONE 6
54 #define CFC_HOME_PHONE 7
55 #define CFC_MOBILE_PHONE 8
56 #define CFC_PAGER 9
57 #define CFC_PIN 10
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_NAME 32 // 0x20 used twice, in first/last name order
63 #define CFC_COMPANY 33
64 #define CFC_DEFAULT_COMM_METHOD 34
65 #define CFC_ADDRESS1 35
66 #define CFC_ADDRESS2 36
67 #define CFC_ADDRESS3 37
68 #define CFC_CITY 38
69 #define CFC_PROVINCE 39
70 #define CFC_POSTAL_CODE 40
71 #define CFC_COUNTRY 41
72 #define CFC_TITLE 42 // 0x2a
73 #define CFC_PUBLIC_KEY 43
74 #define CFC_GROUP_FLAG 44
75 #define CFC_GROUP_LINK 52
76 #define CFC_URL 54 // 0x36
77 #define CFC_PREFIX 55 // 0x37
78 #define CFC_CATEGORY 59 // 0x3B
79 #define CFC_HOME_ADDRESS1 61 // 0x3D
80 #define CFC_HOME_ADDRESS2 62 // 0x3E
81 // If the address 3 isn't mapped then it appears
82 // in the same field as address2 with a space
83 #define CFC_HOME_ADDRESS3 63 // 0x3F
84 #define CFC_NOTES 64 // 0x40
85 #define CFC_USER_DEFINED_1 65 // 0x41
86 #define CFC_USER_DEFINED_2 66 // 0x42
87 #define CFC_USER_DEFINED_3 67 // 0x43
88 #define CFC_USER_DEFINED_4 68 // 0x44
89 #define CFC_HOME_CITY 69 // 0x45
90 #define CFC_HOME_PROVINCE 70 // 0x46
91 #define CFC_HOME_POSTAL_CODE 71 // 0x47
92 #define CFC_HOME_COUNTRY 72 // 0x48
93 #define CFC_IMAGE 77 // 0x4D
94 #define CFC_BIRTHDAY 82 // 0x52
95 #define CFC_ANNIVERSARY 83 // 0x53
96 #define CFC_UNIQUEID 85 // 0x55
97 #define CFC_INVALID_FIELD 255
99 // Contact code to field table
100 static FieldLink<Contact> ContactFieldLinks[] = {
101 { CFC_PHONE, "Phone", 0,0, &Contact::Phone, 0, 0, 0, 0, true },
102 { CFC_FAX, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
103 { CFC_WORK_PHONE, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
104 { CFC_HOME_PHONE, "HomePhone", "homePhone",0, &Contact::HomePhone, 0, 0, 0, 0, true },
105 { CFC_MOBILE_PHONE, "MobilePhone","mobile",0, &Contact::MobilePhone, 0, 0, 0, 0, true },
106 { CFC_PAGER, "Pager", "pager",0, &Contact::Pager, 0, 0, 0, 0, true },
107 { CFC_PIN, "PIN", 0,0, &Contact::PIN, 0, 0, 0, 0, true },
108 { CFC_RADIO, "Radio", 0,0, &Contact::Radio, 0, 0, 0, 0, true },
109 { CFC_WORK_PHONE_2, "WorkPhone2", 0,0, &Contact::WorkPhone2, 0, 0, 0, 0, true },
110 { CFC_HOME_PHONE_2, "HomePhone2", 0,0, &Contact::HomePhone2, 0, 0, 0, 0, true },
111 { CFC_OTHER_PHONE, "OtherPhone", 0,0, &Contact::OtherPhone, 0, 0, 0, 0, true },
112 { CFC_COMPANY, "Company", "o",0, &Contact::Company, 0, 0, 0, 0, true },
113 { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
114 { CFC_ADDRESS1, "WorkAddress1", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
115 { CFC_ADDRESS2, "WorkAddress2", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
116 { CFC_ADDRESS3, "WorkAddress3", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
117 { CFC_CITY, "WorkCity", "l",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
118 { CFC_PROVINCE, "WorkProvince", "st",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
119 { CFC_POSTAL_CODE, "WorkPostalCode", "postalCode",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
120 { CFC_COUNTRY, "WorkCountry", "c", "country", 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
121 { CFC_TITLE, "JobTitle", "title",0, &Contact::JobTitle, 0, 0, 0, 0, true },
122 { CFC_PUBLIC_KEY, "PublicKey", 0,0, &Contact::PublicKey, 0, 0, 0, 0, false },
123 { CFC_URL, "URL", 0,0, &Contact::URL, 0, 0, 0, 0, true },
124 { CFC_PREFIX, "Prefix", 0,0, &Contact::Prefix, 0, 0, 0, 0, true },
125 { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
126 { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
127 { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
128 { CFC_NOTES, "Notes", 0,0, &Contact::Notes, 0, 0, 0, 0, true },
129 { CFC_USER_DEFINED_1, "UserDefined1", 0,0, &Contact::UserDefined1, 0, 0, 0, 0, true },
130 { CFC_USER_DEFINED_2, "UserDefined2", 0,0, &Contact::UserDefined2, 0, 0, 0, 0, true },
131 { CFC_USER_DEFINED_3, "UserDefined3", 0,0, &Contact::UserDefined3, 0, 0, 0, 0, true },
132 { CFC_USER_DEFINED_4, "UserDefined4", 0,0, &Contact::UserDefined4, 0, 0, 0, 0, true },
133 { CFC_HOME_CITY, "HomeCity", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
134 { CFC_HOME_PROVINCE,"HomeProvince", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
135 { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
136 { CFC_HOME_COUNTRY, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
137 { CFC_IMAGE, "Image", 0,0, &Contact::Image, 0, 0, 0, 0, false },
138 { CFC_INVALID_FIELD,"EndOfList", 0, 0, 0, 0, 0, 0, 0, false }
141 Contact::Contact()
142 : RecType(Contact::GetDefaultRecType()),
143 RecordId(0),
144 m_FirstNameSeen(false)
148 Contact::~Contact()
152 const unsigned char* Contact::ParseField(const unsigned char *begin,
153 const unsigned char *end,
154 const IConverter *ic)
156 const CommonField *field = (const CommonField *) begin;
158 // advance and check size
159 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
160 if( begin > end ) // if begin==end, we are ok
161 return begin;
163 if( !btohs(field->size) ) // if field has no size, something's up
164 return begin;
166 // cycle through the type table
167 for( FieldLink<Contact> *b = ContactFieldLinks;
168 b->type != CFC_INVALID_FIELD;
169 b++ )
171 if( b->type == field->type ) {
172 if( b->strMember ) {
173 std::string &s = this->*(b->strMember);
174 s = ParseFieldString(field);
175 if( b->iconvNeeded && ic )
176 s = ic->FromBB(s);
177 return begin; // done!
179 else if( b->postMember && b->postField ) {
180 std::string &s = (this->*(b->postMember)).*(b->postField);
181 s = ParseFieldString(field);
182 if( b->iconvNeeded && ic )
183 s = ic->FromBB(s);
184 return begin;
186 else {
187 break; // fall through to special handling
192 // if not found in the type table, check for special handling
193 switch( field->type )
195 case CFC_EMAIL: {
196 std::string s = ParseFieldString(field);
197 if( ic )
198 s = ic->FromBB(s);
199 EmailAddresses.push_back( s );
201 return begin;
203 case CFC_NAME: {
204 // can be used multiple times, for first/last names
205 std::string *name;
206 if( FirstName.size() || m_FirstNameSeen ) {
207 // first name already filled, use last name
208 name = &LastName;
209 m_FirstNameSeen = false;
211 else {
212 name = &FirstName;
213 m_FirstNameSeen = true;
216 *name = ParseFieldString(field);
217 if( ic )
218 *name = ic->FromBB(*name);
220 return begin;
222 case CFC_GROUP_LINK:
223 // just add the unique ID to the list
224 GroupLinks.push_back(
225 GroupLink(field->u.link.uniqueId,
226 field->u.link.unknown));
227 return begin;
229 case CFC_GROUP_FLAG:
230 // ignore the group flag... the presense of group link items
231 // behaves as the flag in this class
232 return begin;
234 case CFC_CATEGORY: {
235 std::string catstring = ParseFieldString(field);
236 if( ic )
237 catstring = ic->FromBB(catstring);
238 CategoryStr2List(catstring, Categories);
240 return begin;
242 case CFC_BIRTHDAY: {
243 std::string bstring = ParseFieldString(field);
244 Birthday.FromBBString(bstring);
246 return begin;
248 case CFC_ANNIVERSARY: {
249 std::string astring = ParseFieldString(field);
250 Anniversary.FromBBString(astring);
252 return begin;
255 // if still not handled, add to the Unknowns list
256 UnknownField uf;
257 uf.type = field->type;
258 uf.data.assign((const char*)field->u.raw, btohs(field->size));
259 Unknowns.push_back(uf);
261 // return new pointer for next field
262 return begin;
265 void Contact::ParseHeader(const Data &data, size_t &offset)
267 // no header to parse in Contact records
270 // this is called by the RecordParser<> class, which checks size for us
271 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
273 const unsigned char *finish = ParseCommonFields(*this,
274 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
275 offset += finish - (data.GetData() + offset);
278 void Contact::BuildHeader(Data &data, size_t &offset) const
280 // no header in Contact records
284 // BuildFields
286 /// Build fields part of record
288 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
290 data.Zap();
292 // Sanity check: the Blackberry requires at least a name or
293 // a company name for each address record.
294 if( !GetFullName().size() && !Company.size() )
295 throw BadData("Contact must have name or company name.");
297 // check if this is a group link record, and if so, output
298 // the group flag
299 if( GroupLinks.size() )
300 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
302 // special fields not in type table
303 if( FirstName.size() ) {
304 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
305 BuildField(data, offset, CFC_NAME, s);
307 if( LastName.size() ) {
308 if( !FirstName.size() ) {
309 // order matters with first/last name, and if
310 // last name exists, and first name doesn't,
311 // insert blank first name ahead of it
312 BuildField(data, offset, CFC_NAME, "");
314 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
317 // FIXME
318 // // add unknown data
319 // char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
320 // BuildField(data, offset, 0x54, buffer, 8);
322 // With the BlackBerry Storm, I have to add this entry.
323 // Otherwise the uniqueId of this contact is reseted !
324 // The device seems accept the multiple contact with the same uniqueId,
325 // but the synchronization process uses this uniqueId to identify the contact.
326 // add uniqueId
327 BuildField(data, offset, CFC_UNIQUEID, RecordId);
329 // add all email addresses
330 EmailList::const_iterator eai = EmailAddresses.begin();
331 for( ; eai != EmailAddresses.end(); ++eai ) {
332 if( eai->size() ) {
333 BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
337 // cycle through the type table
338 for( FieldLink<Contact> *b = ContactFieldLinks;
339 b->type != CFC_INVALID_FIELD;
340 b++ )
342 // print only fields with data
343 if( b->strMember ) {
344 const std::string &field = this->*(b->strMember);
345 if( field.size() ) {
346 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
347 BuildField(data, offset, b->type, s);
350 else if( b->postMember && b->postField ) {
351 const std::string &field = (this->*(b->postMember)).*(b->postField);
352 if( field.size() ) {
353 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
354 BuildField(data, offset, b->type, s);
359 // save any group links
360 GroupLinksType::const_iterator
361 gb = GroupLinks.begin(), ge = GroupLinks.end();
362 for( ; gb != ge; gb++ ) {
363 Barry::Protocol::GroupLink link;
364 link.uniqueId = htobl(gb->Link);
365 link.unknown = htobs(gb->Unknown);
366 BuildField(data, offset, CFC_GROUP_LINK, link);
369 // save categories
370 if( Categories.size() ) {
371 string store;
372 CategoryList2Str(Categories, store);
373 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
376 // save Birthday and Anniversary
377 if( Birthday.HasData() )
378 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
379 if( Anniversary.HasData() )
380 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
382 // and finally save unknowns
383 UnknownsType::const_iterator
384 ub = Unknowns.begin(), ue = Unknowns.end();
385 for( ; ub != ue; ub++ ) {
386 BuildField(data, offset, *ub);
389 data.ReleaseBuffer(offset);
392 void Contact::Clear()
394 RecType = Contact::GetDefaultRecType();
396 EmailAddresses.clear();
397 Phone.clear();
398 Fax.clear();
399 WorkPhone.clear();
400 HomePhone.clear();
401 MobilePhone.clear();
402 Pager.clear();
403 PIN.clear();
404 Radio.clear();
405 WorkPhone2.clear();
406 HomePhone2.clear();
407 OtherPhone.clear();
408 FirstName.clear();
409 LastName.clear();
410 Company.clear();
411 DefaultCommunicationsMethod.clear();
412 JobTitle.clear();
413 PublicKey.clear();
414 URL.clear();
415 Prefix.clear();
416 Notes.clear();
417 UserDefined1.clear();
418 UserDefined2.clear();
419 UserDefined3.clear();
420 UserDefined4.clear();
421 Image.clear();
423 Birthday.Clear();
424 Anniversary.Clear();
426 WorkAddress.Clear();
427 HomeAddress.Clear();
429 Categories.clear();
431 GroupLinks.clear();
432 Unknowns.clear();
434 m_FirstNameSeen = false;
438 // GetFullName
440 /// Helper function that returns a formatted full name
442 std::string Contact::GetFullName() const
444 std::string Full = FirstName;
445 if( Full.size() && LastName.size() )
446 Full += " ";
447 Full += LastName;
448 return Full;
452 // GetEmail
454 /// Helper function that always returns a valid string. The string
455 /// may be empty if there is no address at the specified index.
457 const std::string& Contact::GetEmail(unsigned int index) const
459 static const std::string blank;
460 if( index < EmailAddresses.size() )
461 return EmailAddresses[index];
462 return blank;
465 void Contact::Dump(std::ostream &os) const
467 ios::fmtflags oldflags = os.setf(ios::left);
468 char fill = os.fill(' ');
470 os << "Contact: 0x" << setbase(16) << GetID()
471 << " (" << (unsigned int)RecType << ")\n";
473 // special fields not in type table
474 os << " " << setw(20) << "FirstName";
475 os << ": " << FirstName << "\n";
476 os << " " << setw(20) << "LastName";
477 os << ": " << LastName << "\n";
479 // cycle through email addresses
480 EmailList::const_iterator eai = EmailAddresses.begin();
481 for( ; eai != EmailAddresses.end(); ++eai ) {
482 if( eai->size() ) {
483 os << " Email : " << *eai << "\n";
487 // cycle through the type table
488 for( FieldLink<Contact> *b = ContactFieldLinks;
489 b->type != CFC_INVALID_FIELD;
490 b++ )
492 const std::string *pField = 0;
493 if( b->strMember ) {
494 pField = &(this->*(b->strMember));
496 else if( b->postMember && b->postField ) {
497 pField = &((this->*(b->postMember)).*(b->postField));
500 // print only fields with data
501 if( pField && pField->size() ) {
502 os << " " << setw(20) << b->name;
503 os << ": " << *pField << "\n";
507 if( Categories.size() ) {
508 string display;
509 CategoryList2Str(Categories, display);
510 os << " Categories : " << display << "\n";
513 // print Birthday and Anniversary
514 if( Birthday.HasData() ) {
515 os << " Birthday : " << Birthday << "\n";
517 if( Anniversary.HasData() ) {
518 os << " Anniversary : " << Anniversary << "\n";
521 // print any group links
522 GroupLinksType::const_iterator
523 gb = GroupLinks.begin(), ge = GroupLinks.end();
524 if( gb != ge )
525 os << " GroupLinks:\n";
526 for( ; gb != ge; gb++ ) {
527 os << " ID: 0x" << setbase(16) << gb->Link << "\n";
530 // and finally print unknowns
531 os << Unknowns;
533 // cleanup the stream
534 os.flags(oldflags);
535 os.fill(fill);
538 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
540 first.clear();
541 last.clear();
543 string::size_type pos = full.find_last_of(' ');
544 if( pos != string::npos ) {
545 // has space, assume last word is last name
546 last = full.c_str() + pos + 1;
547 first = full.substr(0, pos);
549 else {
550 // no space, assume only first name
551 first = full.substr(0);
555 void Contact::CategoryStr2List(const std::string &str,
556 Barry::CategoryList &list)
558 // start fresh
559 list.clear();
561 if( !str.size() )
562 return;
564 // parse the comma-delimited string to a list, stripping away
565 // any white space around each category name
566 string::size_type start = 0, end = 0, delim = str.find(',', start);
567 while( start != string::npos ) {
568 if( delim == string::npos )
569 end = str.size() - 1;
570 else
571 end = delim - 1;
573 // strip surrounding whitespace
574 while( str[start] == ' ' )
575 start++;
576 while( end && str[end] == ' ' )
577 end--;
579 if( start <= end ) {
580 string token = str.substr(start, end-start+1);
581 list.push_back(token);
584 // next
585 start = delim;
586 if( start != string::npos )
587 start++;
588 delim = str.find(',', start);
592 void Contact::CategoryList2Str(const Barry::CategoryList &list,
593 std::string &str)
595 str.clear();
597 Barry::CategoryList::const_iterator i = list.begin();
598 for( ; i != list.end(); ++i ) {
599 if( str.size() )
600 str += ", ";
601 str += *i;
605 } // namespace Barry