- updated time conversion calls to match opensync's latest SVN
[barry.git] / src / record.cc
blob782efbc416aedeb29382bae8458b93a8e884554f
1 ///
2 /// \file record.cc
3 /// Blackberry database record classes. Help translate data
4 /// from data packets to useful structurs, and back.
5 ///
7 /*
8 Copyright (C) 2005-2006, Net Direct Inc. (http://www.netdirect.ca/)
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 See the GNU General Public License in the COPYING file at the
20 root directory of this project for more details.
23 #include "record.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "data.h"
27 #include "base64.h"
28 #include "time.h"
29 #include "error.h"
30 #include <ostream>
31 #include <iomanip>
32 #include <time.h>
33 #include <stdexcept>
35 #define __DEBUG_MODE__
36 #include "debug.h"
38 using namespace std;
40 namespace Barry {
42 std::ostream& operator<<(std::ostream &os, const Message::Address &msga) {
43 os << msga.Name.c_str() << " <" << msga.Email.c_str() << ">";
44 return os;
49 template <class Record>
50 void ParseCommonFields(Record &rec, const void *begin, const void *end)
52 const unsigned char *b = (const unsigned char*) begin;
53 const unsigned char *e = (const unsigned char*) end;
55 while( (b + COMMON_FIELD_HEADER_SIZE) < e )
56 b = rec.ParseField(b, e);
59 void BuildField1900(Data &data, size_t &size, uint8_t type, time_t t)
61 size_t timesize = COMMON_FIELD_MIN1900_SIZE;
62 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + timesize;
63 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
64 CommonField *field = (CommonField *) pd;
66 field->size = timesize;
67 field->type = type;
68 field->u.min1900 = time2min(t);
70 size += fieldsize;
73 void BuildField(Data &data, size_t &size, uint8_t type, char c)
75 size_t strsize = 1;
76 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
77 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
78 CommonField *field = (CommonField *) pd;
80 field->size = strsize;
81 field->type = type;
82 memcpy(field->u.raw, &c, strsize);
84 size += fieldsize;
87 void BuildField(Data &data, size_t &size, uint8_t type, const std::string &str)
89 // include null terminator
90 size_t strsize = str.size() + 1;
91 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
92 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
93 CommonField *field = (CommonField *) pd;
95 field->size = strsize;
96 field->type = type;
97 memcpy(field->u.raw, str.c_str(), strsize);
99 size += fieldsize;
102 void BuildField(Data &data, size_t &size, uint8_t type, const Barry::GroupLink &link)
104 size_t linksize = sizeof(Barry::GroupLink);
105 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + linksize;
106 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
107 CommonField *field = (CommonField *) pd;
109 field->size = linksize;
110 field->type = type;
111 field->u.link = link;
113 size += fieldsize;
117 ///////////////////////////////////////////////////////////////////////////////
118 // CommandTable class
121 CommandTable::CommandTable()
125 CommandTable::~CommandTable()
129 const unsigned char* CommandTable::ParseField(const unsigned char *begin,
130 const unsigned char *end)
132 // check if there is enough data for a header
133 const unsigned char *headend = begin + sizeof(CommandTableField);
134 if( headend > end )
135 return headend;
137 const CommandTableField *field = (const CommandTableField *) begin;
139 // advance and check size
140 begin += COMMAND_FIELD_HEADER_SIZE + field->size;
141 if( begin > end ) // if begin==end, we are ok
142 return begin;
144 if( !field->size ) // if field has no size, something's up
145 return begin;
147 Command command;
148 command.Code = field->code;
149 command.Name.assign((const char *)field->name, field->size);
150 Commands.push_back(command);
151 return begin;
154 void CommandTable::Parse(const Data &data, size_t offset)
156 if( offset >= data.GetSize() )
157 return;
159 const unsigned char *begin = data.GetData() + offset;
160 const unsigned char *end = data.GetData() + data.GetSize();
162 while( begin < end )
163 begin = ParseField(begin, end);
166 void CommandTable::Clear()
168 Commands.clear();
171 unsigned int CommandTable::GetCommand(const std::string &name) const
173 CommandArrayType::const_iterator b = Commands.begin();
174 for( ; b != Commands.end(); b++ )
175 if( b->Name == name )
176 return b->Code;
177 return 0;
180 void CommandTable::Dump(std::ostream &os) const
182 CommandArrayType::const_iterator b = Commands.begin();
183 os << "Command table:\n";
184 for( ; b != Commands.end(); b++ ) {
185 os << " Command: 0x" << setbase(16) << b->Code
186 << " '" << b->Name << "'\n";
193 ///////////////////////////////////////////////////////////////////////////////
194 // DatabaseDatabase class
196 DatabaseDatabase::DatabaseDatabase()
200 DatabaseDatabase::~DatabaseDatabase()
204 template <class RecordType, class FieldType>
205 void DatabaseDatabase::ParseRec(const RecordType &rec, const unsigned char *end)
209 template <class FieldType>
210 const unsigned char* DatabaseDatabase::ParseField(const unsigned char *begin,
211 const unsigned char *end)
213 // check if there is enough data for a header
214 const unsigned char *headend = begin + sizeof(FieldType);
215 if( headend > end )
216 return headend;
218 // get our header
219 const FieldType *field = (const FieldType *) begin;
221 // advance and check size
222 begin += sizeof(FieldType) - sizeof(field->name) + field->nameSize;
223 if( begin > end ) // if begin==end, we are ok
224 return begin;
226 if( !field->nameSize ) // if field has no size, something's up
227 return begin;
229 Database db;
230 db.Number = field->dbNumber;
231 db.RecordCount = field->dbRecordCount;
232 db.Name.assign((const char *)field->name, field->nameSize - 1);
233 Databases.push_back(db);
234 return begin;
237 void DatabaseDatabase::Parse(const Data &data)
239 // check size to make sure we have up to the DBAccess operation byte
240 if( data.GetSize() < (SB_PACKET_DBACCESS_HEADER_SIZE + 1) )
241 return;
243 MAKE_PACKET(pack, data);
244 const unsigned char *begin = 0;
245 const unsigned char *end = data.GetData() + data.GetSize();
247 switch( pack->u.db.u.dbdb.operation )
249 case SB_DBOP_GET_DBDB:
250 // using the new protocol
251 if( data.GetSize() > SB_PACKET_DBDB_HEADER_SIZE ) {
252 begin = (const unsigned char *)
253 &pack->u.db.u.dbdb.field[0];
255 // this while check is ok, since ParseField checks
256 // for header size
257 while( begin < end )
258 begin = ParseField<DBDBField>(begin, end);
260 else
261 dout("Contact: not enough data for parsing");
262 break;
264 case SB_DBOP_OLD_GET_DBDB:
265 // using the old protocol
266 if( data.GetSize() > SB_PACKET_OLD_DBDB_HEADER_SIZE ) {
267 begin = (const unsigned char *)
268 &pack->u.db.u.old_dbdb.field[0];
270 // this while check is ok, since ParseField checks
271 // for header size
272 while( begin < end )
273 begin = ParseField<OldDBDBField>(begin, end);
275 else
276 dout("Contact: not enough data for parsing");
277 break;
279 default:
280 // unknown protocol
281 dout("Unknown protocol");
282 break;
288 void DatabaseDatabase::Clear()
290 Databases.clear();
293 bool DatabaseDatabase::GetDBNumber(const std::string &name,
294 unsigned int &number) const
296 DatabaseArrayType::const_iterator b = Databases.begin();
297 for( ; b != Databases.end(); b++ )
298 if( b->Name == name ) {
299 number = b->Number;
300 return true;
302 return false;
305 bool DatabaseDatabase::GetDBName(unsigned int number,
306 std::string &name) const
308 DatabaseArrayType::const_iterator b = Databases.begin();
309 for( ; b != Databases.end(); b++ )
310 if( b->Number == number ) {
311 name = b->Name;
312 return true;
314 return false;
317 void DatabaseDatabase::Dump(std::ostream &os) const
319 DatabaseArrayType::const_iterator b = Databases.begin();
320 os << "Database database:\n";
321 for( ; b != Databases.end(); b++ ) {
322 os << " Database: 0x" << setbase(16) << b->Number
323 << " '" << b->Name << "' (records: "
324 << setbase(10) << b->RecordCount << ")\n";
329 std::ostream& operator<< (std::ostream &os, const std::vector<UnknownField> &unknowns)
331 std::vector<UnknownField>::const_iterator
332 ub = unknowns.begin(), ue = unknowns.end();
333 if( ub != ue )
334 os << " Unknowns:\n";
335 for( ; ub != ue; ub++ ) {
336 os << " Type: 0x" << setbase(16)
337 << (unsigned int) ub->type
338 << " Data:\n" << Data(ub->data.data(), ub->data.size());
340 return os;
345 ///////////////////////////////////////////////////////////////////////////////
346 // Contact class
348 // Contact field codes
349 #define CFC_EMAIL 1
350 #define CFC_PHONE 2
351 #define CFC_FAX 3
352 #define CFC_WORK_PHONE 6
353 #define CFC_HOME_PHONE 7
354 #define CFC_MOBILE_PHONE 8
355 #define CFC_PAGER 9
356 #define CFC_PIN 10
357 #define CFC_NAME 32 // used twice, in first/last name order
358 #define CFC_COMPANY 33
359 #define CFC_DEFAULT_COMM_METHOD 34
360 #define CFC_ADDRESS1 35
361 #define CFC_ADDRESS2 36
362 #define CFC_ADDRESS3 37
363 #define CFC_CITY 38
364 #define CFC_PROVINCE 39
365 #define CFC_POSTAL_CODE 40
366 #define CFC_COUNTRY 41
367 #define CFC_TITLE 42
368 #define CFC_PUBLIC_KEY 43
369 #define CFC_GROUP_FLAG 44
370 #define CFC_GROUP_LINK 52
371 #define CFC_NOTES 64
372 #define CFC_INVALID_FIELD 255
374 // Contact code to field table
375 template <class Record>
376 struct FieldLink
378 int type;
379 char *name;
380 char *ldif;
381 char *objectClass;
382 std::string Record::* strMember; // FIXME - find a more general
383 Message::Address Record::* addrMember; // way to do this...
384 time_t Record::* timeMember;
387 FieldLink<Contact> ContactFieldLinks[] = {
388 { CFC_EMAIL, "Email", "mail",0, &Contact::Email, 0, 0 },
389 { CFC_PHONE, "Phone", 0,0, &Contact::Phone, 0, 0 },
390 { CFC_FAX, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0 },
391 { CFC_WORK_PHONE, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone, 0, 0 },
392 { CFC_HOME_PHONE, "HomePhone", "homePhone",0, &Contact::HomePhone, 0, 0 },
393 { CFC_MOBILE_PHONE, "MobilePhone","mobile",0, &Contact::MobilePhone, 0, 0 },
394 { CFC_PAGER, "Pager", "pager",0, &Contact::Pager, 0, 0 },
395 { CFC_PIN, "PIN", 0,0, &Contact::PIN, 0, 0 },
396 { CFC_COMPANY, "Company", "o",0, &Contact::Company, 0, 0 },
397 { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod, 0, 0 },
398 { CFC_ADDRESS1, "Address1", 0,0, &Contact::Address1, 0, 0 },
399 { CFC_ADDRESS2, "Address2", 0,0, &Contact::Address2, 0, 0 },
400 { CFC_ADDRESS3, "Address3", 0,0, &Contact::Address3, 0, 0 },
401 { CFC_CITY, "City", "l",0, &Contact::City, 0, 0 },
402 { CFC_PROVINCE, "Province", "st",0, &Contact::Province, 0, 0 },
403 { CFC_POSTAL_CODE, "PostalCode", "postalCode",0, &Contact::PostalCode, 0, 0 },
404 { CFC_COUNTRY, "Country", "c", "country", &Contact::Country, 0, 0 },
405 { CFC_TITLE, "Title", "title",0, &Contact::Title, 0, 0 },
406 { CFC_PUBLIC_KEY, "PublicKey", 0,0, &Contact::PublicKey, 0, 0 },
407 { CFC_NOTES, "Notes", 0,0, &Contact::Notes, 0, 0 },
408 { CFC_INVALID_FIELD,"EndOfList", 0, 0, 0 }
411 size_t Contact::GetOldProtocolRecordSize()
413 return sizeof(Barry::OldContactRecord);
416 size_t Contact::GetProtocolRecordSize()
418 return sizeof(Barry::ContactRecord);
421 Contact::Contact()
422 : RecordId(0)
426 Contact::~Contact()
430 const unsigned char* Contact::ParseField(const unsigned char *begin,
431 const unsigned char *end)
433 const CommonField *field = (const CommonField *) begin;
435 // advance and check size
436 begin += COMMON_FIELD_HEADER_SIZE + field->size;
437 if( begin > end ) // if begin==end, we are ok
438 return begin;
440 if( !field->size ) // if field has no size, something's up
441 return begin;
443 // cycle through the type table
444 for( FieldLink<Contact> *b = ContactFieldLinks;
445 b->type != CFC_INVALID_FIELD;
446 b++ )
448 if( b->type == field->type ) {
449 std::string &s = this->*(b->strMember);
450 s.assign((const char *)field->u.raw, field->size-1);
451 return begin; // done!
455 // if not found in the type table, check for special handling
456 switch( field->type )
458 case CFC_NAME: {
459 // can be used multiple times, for first/last names
460 std::string *name;
461 if( FirstName.size() )
462 // first name already filled, use last name
463 name = &LastName;
464 else
465 name = &FirstName;
467 name->assign((const char*)field->u.raw, field->size-1);
469 return begin;
471 case CFC_GROUP_LINK:
472 // just add the unique ID to the list
473 GroupLinks.push_back(
474 GroupLink(field->u.link.uniqueId,
475 field->u.link.unknown));
476 return begin;
478 case CFC_GROUP_FLAG:
479 // ignore the group flag... the presense of group link items
480 // behaves as the flag in this class
481 return begin;
484 // if still not handled, add to the Unknowns list
485 UnknownField uf;
486 uf.type = field->type;
487 uf.data.assign((const char*)field->u.raw, field->size);
488 Unknowns.push_back(uf);
490 // return new pointer for next field
491 return begin;
494 // this is called by the RecordParser<> class, which checks size for us
495 void Contact::Parse(const Data &data, size_t offset, unsigned int operation)
497 const void *begin = 0;
498 switch( operation )
500 case SB_DBOP_GET_RECORDS:
502 // using the new protocol
503 // save the contact record ID
504 MAKE_RECORD(const Barry::ContactRecord, contact, data, offset);
505 RecordId = contact->uniqueId;
506 begin = &contact->field[0];
507 break;
510 case SB_DBOP_OLD_GET_RECORDS_REPLY:
512 // using the old protocol
513 // save the contact record ID
514 MAKE_RECORD(const Barry::OldContactRecord, contact, data, offset);
515 RecordId = contact->uniqueId;
516 begin = &contact->field[0];
517 break;
521 ParseCommonFields(*this, begin, data.GetData() + data.GetSize());
525 // Build
527 /// Build a raw protocol packet based on data in the class.
529 void Contact::Build(Data &data, size_t offset) const
531 data.Zap();
533 // uploading always seems to use the old record
534 size_t size = offset + OLD_CONTACT_RECORD_HEADER_SIZE;
535 unsigned char *pd = data.GetBuffer(size);
536 MAKE_RECORD_PTR(Barry::OldContactRecord, contact, pd, offset);
538 contact->uniqueId = RecordId;
539 contact->unknown = 1;
541 // check if this is a group link record, and if so, output
542 // the group flag
543 if( GroupLinks.size() )
544 BuildField(data, size, CFC_GROUP_FLAG, 'G');
546 // special fields not in type table
547 if( FirstName.size() )
548 BuildField(data, size, CFC_NAME, FirstName);
549 if( LastName.size() )
550 BuildField(data, size, CFC_NAME, LastName);
552 // cycle through the type table
553 for( FieldLink<Contact> *b = ContactFieldLinks;
554 b->type != CFC_INVALID_FIELD;
555 b++ )
557 // print only fields with data
558 const std::string &field = this->*(b->strMember);
559 if( field.size() ) {
560 BuildField(data, size, b->type, field);
564 // save any group links
565 GroupLinksType::const_iterator
566 gb = GroupLinks.begin(), ge = GroupLinks.end();
567 for( ; gb != ge; gb++ ) {
568 Barry::GroupLink link;
569 link.uniqueId = gb->Link;
570 link.unknown = gb->Unknown;
571 BuildField(data, size, CFC_GROUP_LINK, link);
574 // and finally save unknowns
575 UnknownsType::const_iterator
576 ub = Unknowns.begin(), ue = Unknowns.end();
577 for( ; ub != ue; ub++ ) {
578 BuildField(data, size, ub->type, ub->data);
581 data.ReleaseBuffer(size);
584 void Contact::Clear()
586 Email.clear();
587 Phone.clear();
588 Fax.clear();
589 WorkPhone.clear();
590 HomePhone.clear();
591 MobilePhone.clear();
592 Pager.clear();
593 PIN.clear();
594 FirstName.clear();
595 LastName.clear();
596 Company.clear();
597 DefaultCommunicationsMethod.clear();
598 Address1.clear();
599 Address2.clear();
600 Address3.clear();
601 City.clear();
602 Province.clear();
603 PostalCode.clear();
604 Country.clear();
605 Title.clear();
606 PublicKey.clear();
607 Notes.clear();
609 GroupLinks.clear();
610 Unknowns.clear();
614 // GetPostalAddress
616 /// Format a mailing address, handling missing fields.
618 std::string Contact::GetPostalAddress() const
620 std::string address = Address1;
621 if( Address2.size() ) {
622 if( address.size() )
623 address += "\n";
624 address += Address2;
626 if( Address3.size() ) {
627 if( address.size() )
628 address += "\n";
629 address += Address3;
631 if( address.size() )
632 address += "\n";
633 if( City.size() )
634 address += City + " ";
635 if( Province.size() )
636 address += Province + " ";
637 if( Country.size() )
638 address += Country;
639 if( address.size() )
640 address += "\n";
641 if( PostalCode.size() )
642 address += PostalCode;
644 return address;
647 void Contact::Dump(std::ostream &os) const
649 ios::fmtflags oldflags = os.setf(ios::left);
650 char fill = os.fill(' ');
652 os << "Contact: 0x" << setbase(16) << GetID() << "\n";
654 // special fields not in type table
655 os << " " << setw(20) << "FirstName";
656 os << ": " << FirstName << "\n";
657 os << " " << setw(20) << "LastName";
658 os << ": " << LastName << "\n";
660 // cycle through the type table
661 for( FieldLink<Contact> *b = ContactFieldLinks;
662 b->type != CFC_INVALID_FIELD;
663 b++ )
665 // print only fields with data
666 const std::string &field = this->*(b->strMember);
667 if( field.size() ) {
668 os << " " << setw(20) << b->name;
669 os << ": " << field << "\n";
673 // print any group links
674 GroupLinksType::const_iterator
675 gb = GroupLinks.begin(), ge = GroupLinks.end();
676 if( gb != ge )
677 os << " GroupLinks:\n";
678 for( ; gb != ge; gb++ ) {
679 os << " ID: 0x" << setbase(16) << gb->Link << "\n";
682 // and finally print unknowns
683 os << Unknowns;
685 // cleanup the stream
686 os.flags(oldflags);
687 os.fill(fill);
691 // DumpLdif
693 /// Output contact data to os in LDAP LDIF format.
694 /// This is hardcoded for now. Someday we should support mapping
695 /// of fields.
697 void Contact::DumpLdif(std::ostream &os, const std::string &baseDN) const
699 ios::fmtflags oldflags = os.setf(ios::left);
700 char fill = os.fill(' ');
702 if( FirstName.size() == 0 && LastName.size() == 0 )
703 return; // nothing to do
705 std::string FullName = FirstName + " " + LastName;
706 os << "# Contact 0x" << setbase(16) << GetID() << FullName << "\n";
707 os << "dn: " << FullName << "," << baseDN << "\n";
708 os << "objectClass: inetOrgPerson\n";
709 os << "displayName: " << FullName << "\n";
710 os << "cn: " << FullName << "\n";
711 if( LastName.size() )
712 os << "sn: " << LastName << "\n";
713 if( FirstName.size() )
714 os << "givenName: " << FirstName << "\n";
716 // cycle through the type table
717 for( FieldLink<Contact> *b = ContactFieldLinks;
718 b->type != CFC_INVALID_FIELD;
719 b++ )
721 // print only fields with data
722 const std::string &field = this->*(b->strMember);
723 if( b->ldif && field.size() ) {
724 os << b->ldif << ": " << field << "\n";
725 if( b->objectClass )
726 os << "objectClass: " << b->objectClass << "\n";
730 std::string b64;
731 if( Address1.size() && base64_encode(Address1, b64) )
732 os << "street:: " << b64 << "\n";
734 std::string FullAddress = GetPostalAddress();
735 if( FullAddress.size() && base64_encode(FullAddress, b64) )
736 os << "postalAddress:: " << b64 << "\n";
738 if( Notes.size() && base64_encode(Notes, b64) )
739 os << "note:: " << b64 << "\n";
742 // print any group links
743 GroupLinksType::const_iterator
744 gb = GroupLinks.begin(), ge = GroupLinks.end();
745 if( gb != ge )
746 os << " GroupLinks:\n";
747 for( ; gb != ge; gb++ ) {
748 os << " ID: 0x" << setbase(16) << gb->Link << "\n";
752 // last line must be empty
753 os << "\n";
755 // cleanup the stream
756 os.flags(oldflags);
757 os.fill(fill);
760 bool Contact::ReadLdif(std::istream &is)
762 string line;
764 // start fresh
765 Clear();
767 // search for beginning dn: line
768 bool found = false;
769 while( getline(is, line) ) {
770 if( strncmp(line.c_str(), "dn: ", 4) == 0 ) {
771 found = true;
772 break;
775 if( !found )
776 return false;
778 // storage for various name styles
779 string cn, displayName, sn, givenName;
780 string coded, decode;
781 string *b64field = 0;
783 // read ldif lines until empty line is found
784 while( getline(is, line) && line.size() ) {
786 if( b64field ) {
787 // processing a base64 encoded field
788 if( line[0] == ' ' ) {
789 coded += "\n";
790 coded += line;
791 continue;
793 else {
794 // end of base64 block
795 base64_decode(coded, decode);
796 *b64field = decode;
797 coded.clear();
798 b64field = 0;
800 // fall through to process new line
803 if( strncmp(line.c_str(), "cn: ", 4) == 0 ) {
804 cn = line.c_str() + 4; // full name
806 else if( strncmp(line.c_str(), "displayName: ", 13) == 0 ) {
807 displayName = line.c_str() + 13; // full name
809 else if( strncmp(line.c_str(), "sn: ", 4) == 0 ) {
810 sn = line.c_str() + 4; // last name
812 else if( strncmp(line.c_str(), "givenName: ", 11) == 0 ) {
813 givenName = line.c_str() + 11; // first name
815 else if( strncmp(line.c_str(), "street:: ", 9) == 0 ) {
816 coded = line.substr(9);
817 b64field = &Address1;
820 // else if( strncmp(line.c_str(), "postalAddress:: ", 16) == 0 ) {
821 // std::string FullAddress = GetPostalAddress();
822 // if( FullAddress.size() && base64_encode(FullAddress, b64) )
823 // os << "postalAddress:: " << b64 << "\n";
824 // }
825 else if( strncmp(line.c_str(), "note:: ", 7) == 0 ) {
826 coded = line.substr(7);
827 b64field = &Notes;
829 else {
831 // cycle through the type table
832 for( FieldLink<Contact> *b = ContactFieldLinks;
833 b->type != CFC_INVALID_FIELD;
834 b++ )
836 // read fields
837 if( b->ldif ) {
838 std::string &field = this->*(b->strMember);
839 std::string key = b->ldif;
840 key += ": ";
842 if( strncmp(line.c_str(), key.c_str(), key.size()) == 0 ) {
843 field = line.c_str() + key.size();
844 break;
851 if( b64field ) {
852 // clean up base64 decoding
853 base64_decode(coded, decode);
854 *b64field = decode;
855 coded.clear();
856 b64field = 0;
859 // find the best match for name... prefer sn/givenName if available
860 if( sn.size() ) {
861 LastName = sn;
863 if( givenName.size() ) {
864 FirstName = givenName;
867 if( !LastName.size() || !FirstName.size() ) {
868 string first, last;
870 // still don't have a complete name, check cn first
871 if( cn.size() ) {
872 SplitName(cn, first, last);
873 if( !LastName.size() && last.size() )
874 LastName = last;
875 if( !FirstName.size() && first.size() )
876 FirstName = first;
879 // displayName is last chance
880 if( displayName.size() ) {
881 SplitName(displayName, first, last);
882 if( !LastName.size() && last.size() )
883 LastName = last;
884 if( !FirstName.size() && first.size() )
885 FirstName = first;
889 return LastName.size() && FirstName.size();
892 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
894 first.clear();
895 last.clear();
897 string::size_type pos = full.find_last_of(' ');
898 if( pos != string::npos ) {
899 // has space, assume last word is last name
900 last = full.c_str() + pos + 1;
901 first = full.substr(0, pos);
903 else {
904 // no space, assume only first name
905 first = full.substr(0);
911 ///////////////////////////////////////////////////////////////////////////////
912 // Message class
915 // Email / message field codes
916 #define MFC_TO 0x01 // can occur multiple times
917 #define MFC_FROM 0x05
918 #define MFC_SUBJECT 0x0b
919 #define MFC_BODY 0x0c
920 #define MFC_END 0xffff
922 FieldLink<Message> MessageFieldLinks[] = {
923 { MFC_TO, "To", 0, 0, 0, &Message::To, 0 },
924 { MFC_FROM, "From", 0, 0, 0, &Message::From, 0 },
925 { MFC_SUBJECT, "Subject", 0, 0, &Message::Subject, 0, 0 },
926 { MFC_BODY, "Body", 0, 0, &Message::Body, 0, 0 },
927 { MFC_END, "End of List",0, 0, 0, 0, 0 }
930 size_t Message::GetOldProtocolRecordSize()
932 return sizeof(Barry::OldMessageRecord);
935 size_t Message::GetProtocolRecordSize()
937 return sizeof(Barry::MessageRecord);
940 Message::Message()
944 Message::~Message()
948 const unsigned char* Message::ParseField(const unsigned char *begin,
949 const unsigned char *end)
951 const CommonField *field = (const CommonField *) begin;
953 // advance and check size
954 begin += COMMON_FIELD_HEADER_SIZE + field->size;
955 if( begin > end ) // if begin==end, we are ok
956 return begin;
958 if( !field->size ) // if field has no size, something's up
959 return begin;
961 // cycle through the type table
962 for( FieldLink<Message> *b = MessageFieldLinks;
963 b->type != MFC_END;
964 b++ )
966 if( b->type == field->type ) {
967 if( b->strMember ) {
968 // parse regular string
969 std::string &s = this->*(b->strMember);
970 s.assign((const char *)field->u.raw, field->size-1);
971 return begin; // done!
973 else if( b->addrMember ) {
974 // parse email address
975 // get dual name+addr string first
976 const char *fa = (const char*)field->u.addr.addr;
977 std::string dual(fa, field->size - sizeof(field->u.addr.unknown));
979 // assign first string, using null terminator...letting std::string add it for us if it doesn't exist
980 Address &a = this->*(b->addrMember);
981 a.Name = dual.c_str();
983 // assign second string, using first size as starting point
984 a.Email = dual.c_str() + a.Name.size() + 1;
989 return begin;
992 void Message::Parse(const Data &data, size_t offset, unsigned int operation)
994 const void *begin = 0;
995 switch( operation )
997 case SB_DBOP_GET_RECORDS:
999 MAKE_RECORD(const Barry::MessageRecord, msg, data, offset);
1000 begin = &msg->field[0];
1001 break;
1004 case SB_DBOP_OLD_GET_RECORDS_REPLY:
1006 MAKE_RECORD(const Barry::OldMessageRecord, msg, data, offset);
1007 begin = &msg->field[0];
1008 break;
1012 ParseCommonFields(*this, begin, data.GetData() + data.GetSize());
1015 void Message::Clear()
1017 From.Name.clear();
1018 From.Email.clear();
1019 To.Name.clear();
1020 To.Email.clear();
1021 Cc.Name.clear();
1022 Cc.Email.clear();
1023 Subject.clear();
1024 Body.clear();
1027 // dump message in mbox format
1028 void Message::Dump(std::ostream &os) const
1030 // FIXME - use current time until we figure out the date headers
1031 time_t fixme = time(NULL);
1033 os << "From " << (From.Email.size() ? From.Email.c_str() : "unknown")
1034 << " " << ctime(&fixme);
1035 os << "Date: " << ctime(&fixme);
1036 os << "From: " << From << "\n";
1037 if( To.Email.size() )
1038 os << "To: " << To << "\n";
1039 if( Cc.Email.size() )
1040 os << "Cc: " << Cc << "\n";
1041 if( Subject.size() )
1042 os << "Subject: " << Subject << "\n";
1043 os << "\n";
1044 for( std::string::const_iterator i = Body.begin();
1045 i != Body.end() && *i;
1046 i++)
1048 if( *i == '\r' )
1049 os << '\n';
1050 else
1051 os << *i;
1053 os << "\n\n";
1058 ///////////////////////////////////////////////////////////////////////////////
1059 // Calendar class
1061 // calendar field codes
1062 #define CALFC_APPT_TYPE_FLAG 0x01
1063 #define CALFC_SUBJECT 0x02
1064 #define CALFC_NOTES 0x03
1065 #define CALFC_LOCATION 0x04
1066 #define CALFC_NOTIFICATION_TIME 0x05
1067 #define CALFC_START_TIME 0x06
1068 #define CALFC_END_TIME 0x07
1069 #define CALFC_RECURRANCE_DATA 0x0c
1070 #define CALFC_VERSION_DATA 0x10
1071 #define CALFC_NOTIFICATION_DATA 0x1a
1072 #define CALFC_ALLDAYEVENT_FLAG 0xff
1073 #define CALFC_END 0xffff
1075 FieldLink<Calendar> CalendarFieldLinks[] = {
1076 { CALFC_SUBJECT, "Subject", 0, 0, &Calendar::Subject, 0, 0 },
1077 { CALFC_NOTES, "Notes", 0, 0, &Calendar::Notes, 0, 0 },
1078 { CALFC_LOCATION, "Location", 0, 0, &Calendar::Location, 0, 0 },
1079 { CALFC_NOTIFICATION_TIME,"Notification Time",0,0, 0, 0, &Calendar::NotificationTime },
1080 { CALFC_START_TIME, "Start Time", 0, 0, 0, 0, &Calendar::StartTime },
1081 { CALFC_END_TIME, "End Time", 0, 0, 0, 0, &Calendar::EndTime },
1082 { CALFC_END, "End of List",0, 0, 0, 0, 0 }
1085 size_t Calendar::GetOldProtocolRecordSize()
1087 return sizeof(Barry::OldCalendarRecord);
1090 size_t Calendar::GetProtocolRecordSize()
1092 return sizeof(Barry::CalendarRecord);
1095 Calendar::Calendar()
1096 : Recurring(false),
1097 AllDayEvent(false)
1099 Clear();
1102 Calendar::~Calendar()
1106 const unsigned char* Calendar::ParseField(const unsigned char *begin,
1107 const unsigned char *end)
1109 const CommonField *field = (const CommonField *) begin;
1111 // advance and check size
1112 begin += COMMON_FIELD_HEADER_SIZE + field->size;
1113 if( begin > end ) // if begin==end, we are ok
1114 return begin;
1116 if( !field->size ) // if field has no size, something's up
1117 return begin;
1119 // cycle through the type table
1120 for( FieldLink<Calendar> *b = CalendarFieldLinks;
1121 b->type != CALFC_END;
1122 b++ )
1124 if( b->type == field->type ) {
1125 if( b->strMember ) {
1126 std::string &s = this->*(b->strMember);
1127 s.assign((const char *)field->u.raw, field->size-1);
1128 return begin; // done!
1130 else if( b->timeMember ) {
1131 time_t &t = this->*(b->timeMember);
1132 t = min2time(field->u.min1900);
1133 return begin;
1138 // handle special cases
1139 switch( field->type )
1141 case CALFC_APPT_TYPE_FLAG:
1142 switch( field->u.raw[0] )
1144 case 'a': // regular non-recurring appointment
1145 Recurring = false;
1146 return begin;
1148 case '*': // recurring appointment
1149 Recurring = true;
1150 return begin;
1152 default:
1153 throw BError("Calendar::ParseField: unknown appointment type");
1155 break;
1157 case CALFC_ALLDAYEVENT_FLAG:
1158 AllDayEvent = field->u.raw[0] == 1;
1159 return begin;
1162 // if still not handled, add to the Unknowns list
1163 UnknownField uf;
1164 uf.type = field->type;
1165 uf.data.assign((const char*)field->u.raw, field->size);
1166 Unknowns.push_back(uf);
1168 // return new pointer for next field
1169 return begin;
1172 void Calendar::Parse(const Data &data, size_t offset, unsigned int operation)
1174 const void *begin = 0;
1176 switch( operation )
1178 case SB_DBOP_GET_RECORDS:
1179 // using the new protocol
1180 // save the contact record ID
1181 throw std::logic_error("New Calendar: Not yet implemented");
1182 // RecordId = pack->u.db.u.calendar.uniqueId;
1183 // begin = &pack->u.db.u.calendar.field[0];
1184 break;
1186 case SB_DBOP_OLD_GET_RECORDS_REPLY:
1188 // using the old protocol
1189 // save the contact record ID
1190 MAKE_RECORD(const Barry::OldCalendarRecord, cal, data, offset);
1191 RecordId = cal->uniqueId;
1192 begin = &cal->field[0];
1193 break;
1197 ParseCommonFields(*this, begin, data.GetData() + data.GetSize());
1201 // Build
1203 /// Build a raw protocol packet based on data in the class.
1205 void Calendar::Build(Data &data, size_t offset) const
1207 data.Zap();
1209 // uploading always seems to use the old record
1210 size_t size = offset + OLD_CALENDAR_RECORD_HEADER_SIZE;
1211 unsigned char *pd = data.GetBuffer(size);
1212 MAKE_RECORD_PTR(Barry::OldCalendarRecord, contact, pd, offset);
1214 contact->uniqueId = RecordId;
1215 contact->unknown = 1;
1217 // output the type first
1218 BuildField(data, size, CALFC_APPT_TYPE_FLAG, Recurring ? '*' : 'a');
1220 // output all day event flag only if set
1221 if( AllDayEvent )
1222 BuildField(data, size, CALFC_ALLDAYEVENT_FLAG, (char)1);
1224 // cycle through the type table
1225 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
1226 b->type != CALFC_END;
1227 b++ )
1229 if( b->strMember ) {
1230 const std::string &s = this->*(b->strMember);
1231 if( s.size() )
1232 BuildField(data, size, b->type, s);
1234 else if( b->timeMember ) {
1235 time_t t = this->*(b->timeMember);
1236 if( t > 0 )
1237 BuildField1900(data, size, b->type, t);
1241 // and finally save unknowns
1242 UnknownsType::const_iterator
1243 ub = Unknowns.begin(), ue = Unknowns.end();
1244 for( ; ub != ue; ub++ ) {
1245 BuildField(data, size, ub->type, ub->data);
1248 data.ReleaseBuffer(size);
1251 void Calendar::Clear()
1253 Subject.clear();
1254 Notes.clear();
1255 Location.clear();
1256 NotificationTime = StartTime = EndTime = 0;
1257 Unknowns.clear();
1260 void Calendar::Dump(std::ostream &os) const
1262 os << "Calendar entry: 0x" << setbase(16) << RecordId << "\n";
1263 os << " Recurring: " << (Recurring ? "yes" : "no") << "\n";
1264 os << " All Day Event: " << (AllDayEvent ? "yes" : "no") << "\n";
1266 // cycle through the type table
1267 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
1268 b->type != CALFC_END;
1269 b++ )
1271 if( b->strMember ) {
1272 const std::string &s = this->*(b->strMember);
1273 if( s.size() )
1274 os << " " << b->name << ": " << s << "\n";
1276 else if( b->timeMember ) {
1277 time_t t = this->*(b->timeMember);
1278 if( t > 0 )
1279 os << " " << b->name << ": " << ctime(&t);
1283 // print any unknowns
1284 os << Unknowns;
1288 ///////////////////////////////////////////////////////////////////////////////
1289 // ServiceBookConfig class
1291 // service book packed field codes
1292 #define SBFCC_END 0xffff
1294 FieldLink<ServiceBookConfig> ServiceBookConfigFieldLinks[] = {
1295 // { SBFC_DSID, "DSID", 0, 0, &ServiceBook::DSID, 0, 0 },
1296 { SBFCC_END, "End of List",0, 0, 0, 0, 0 }
1299 ServiceBookConfig::ServiceBookConfig()
1300 : Format(0)
1302 Clear();
1305 ServiceBookConfig::~ServiceBookConfig()
1309 const unsigned char* ServiceBookConfig::ParseField(const unsigned char *begin,
1310 const unsigned char *end)
1312 const void *raw;
1313 uint16_t size, type;
1315 switch( Format )
1317 case 0x02:
1319 const PackedField_02 *field = (const PackedField_02 *) begin;
1320 raw = field->raw;
1321 size = field->size;
1322 type = field->type;
1323 begin += PACKED_FIELD_02_HEADER_SIZE + size;
1325 break;
1327 case 0x10:
1329 const PackedField_10 *field = (const PackedField_10 *) begin;
1330 raw = field->raw;
1331 size = field->size;
1332 type = field->type;
1333 begin += PACKED_FIELD_10_HEADER_SIZE + size;
1335 break;
1337 default:
1338 eout("Unknown packed field format" << Format);
1339 return begin + 1;
1343 // check size
1344 if( begin > end ) // if begin==end, we are ok
1345 return begin;
1347 if( !size ) // if field has no size, something's up
1348 return begin;
1350 // cycle through the type table
1351 for( FieldLink<ServiceBookConfig> *b = ServiceBookConfigFieldLinks;
1352 b->type != SBFCC_END;
1353 b++ )
1355 if( b->type == type ) {
1356 if( b->strMember ) {
1357 std::string &s = this->*(b->strMember);
1358 s.assign((const char *)raw, size-1);
1359 return begin; // done!
1365 // handle special cases
1366 switch( type )
1371 // if still not handled, add to the Unknowns list
1372 UnknownField uf;
1373 uf.type = type;
1374 uf.data.assign((const char*)raw, size);
1375 Unknowns.push_back(uf);
1377 // return new pointer for next field
1378 return begin;
1381 void ServiceBookConfig::Parse(const Data &data, size_t offset, unsigned int operation)
1383 const void *begin = 0;
1385 switch( operation )
1387 case SB_DBOP_GET_RECORDS:
1388 case SB_DBOP_OLD_GET_RECORDS_REPLY:
1389 default: // FIXME - any operation is ok here... we don't know yet if it makes a difference
1391 // using the old protocol
1392 // save the contact record ID
1393 MAKE_RECORD(const Barry::ServiceBookConfigField, sbc, data, offset);
1394 Format = sbc->format;
1395 begin = &sbc->fields[0];
1396 break;
1400 ParseCommonFields(*this, begin, data.GetData() + data.GetSize());
1404 // Build
1406 /// Build a raw protocol packet based on data in the class.
1408 void ServiceBookConfig::Build(Data &data, size_t offset) const
1410 throw std::logic_error("ServiceBookConfig::Build not yet implemented");
1413 void ServiceBookConfig::Clear()
1415 Unknowns.clear();
1418 void ServiceBookConfig::Dump(std::ostream &os) const
1420 os << " ServiceBookConfig Format: " << setbase(16) << (uint16_t)Format << "\n";
1422 // cycle through the type table
1423 for( const FieldLink<ServiceBookConfig> *b = ServiceBookConfigFieldLinks;
1424 b->type != SBFCC_END;
1425 b++ )
1427 if( b->strMember ) {
1428 const std::string &s = this->*(b->strMember);
1429 if( s.size() )
1430 os << " " << b->name << ": " << s << "\n";
1432 else if( b->timeMember ) {
1433 time_t t = this->*(b->timeMember);
1434 if( t > 0 )
1435 os << " " << b->name << ": " << ctime(&t);
1439 // print any unknowns
1440 os << Unknowns;
1441 os << " ------------------- End of Config Field\n";
1445 ///////////////////////////////////////////////////////////////////////////////
1446 // ServiceBook class
1448 // service book field codes
1449 #define SBFC_OLD_NAME 0x01
1450 #define SBFC_HIDDEN_NAME 0x02
1451 #define SBFC_NAME 0x03
1452 #define SBFC_OLD_UNIQUE_ID 0x06
1453 #define SBFC_UNIQUE_ID 0x07
1454 #define SBFC_CONTENT_ID 0x08
1455 #define SBFC_CONFIG 0x09
1456 #define SBFC_OLD_DESC 0x32
1457 #define SBFC_DESCRIPTION 0x0f
1458 #define SBFC_DSID 0xa1
1459 #define SBFC_BES_DOMAIN 0xa2
1460 #define SBFC_USER_ID 0xa3
1461 #define SBFC_END 0xffff
1463 FieldLink<ServiceBook> ServiceBookFieldLinks[] = {
1464 { SBFC_HIDDEN_NAME, "Hidden Name",0, 0, &ServiceBook::HiddenName, 0, 0 },
1465 { SBFC_DSID, "DSID", 0, 0, &ServiceBook::DSID, 0, 0 },
1466 { SBFC_END, "End of List",0, 0, 0, 0, 0 }
1469 size_t ServiceBook::GetOldProtocolRecordSize()
1471 return sizeof(Barry::OldServiceBookRecord);
1474 size_t ServiceBook::GetProtocolRecordSize()
1476 throw std::logic_error("ServiceBook::GetProtocolRecordSize not yet implemented");
1477 // return sizeof(Barry::ServiceBookRecord);
1478 return 0;
1481 ServiceBook::ServiceBook()
1482 : NameType(SBFC_OLD_NAME),
1483 DescType(SBFC_OLD_DESC),
1484 UniqueIdType(SBFC_OLD_UNIQUE_ID),
1485 RecordId(0)
1487 Clear();
1490 ServiceBook::~ServiceBook()
1494 const unsigned char* ServiceBook::ParseField(const unsigned char *begin,
1495 const unsigned char *end)
1497 const CommonField *field = (const CommonField *) begin;
1499 // advance and check size
1500 begin += COMMON_FIELD_HEADER_SIZE + field->size;
1501 if( begin > end ) // if begin==end, we are ok
1502 return begin;
1504 if( !field->size ) // if field has no size, something's up
1505 return begin;
1507 // cycle through the type table
1508 for( FieldLink<ServiceBook> *b = ServiceBookFieldLinks;
1509 b->type != SBFC_END;
1510 b++ )
1512 if( b->type == field->type ) {
1513 if( b->strMember ) {
1514 std::string &s = this->*(b->strMember);
1515 s.assign((const char *)field->u.raw, field->size-1);
1516 return begin; // done!
1518 else if( b->timeMember ) {
1519 time_t &t = this->*(b->timeMember);
1520 t = min2time(field->u.min1900);
1521 return begin;
1526 // handle special cases
1527 switch( field->type )
1529 case SBFC_OLD_NAME: // strings with old/new type codes
1530 case SBFC_NAME:
1531 Name.assign((const char *)field->u.raw, field->size-1);
1532 NameType = field->type;
1533 return begin;
1535 case SBFC_OLD_DESC:
1536 case SBFC_DESCRIPTION:
1537 Description.assign((const char *)field->u.raw, field->size-1);
1538 DescType = field->type;
1539 return begin;
1541 case SBFC_OLD_UNIQUE_ID:
1542 case SBFC_UNIQUE_ID:
1543 UniqueId.assign((const char *)field->u.raw, field->size);
1544 UniqueIdType = field->type;
1545 return begin;
1547 case SBFC_CONTENT_ID:
1548 ContentId.assign((const char *)field->u.raw, field->size);
1549 return begin;
1551 case SBFC_BES_DOMAIN:
1552 BesDomain.assign((const char *)field->u.raw, field->size);
1553 return begin;
1555 case SBFC_CONFIG:
1557 Data config((const void *)field->u.raw, field->size);
1558 Config.Parse(config, 0, 0);
1560 break; // break here so raw packet is still visible in dump
1561 // return begin;
1564 // if still not handled, add to the Unknowns list
1565 UnknownField uf;
1566 uf.type = field->type;
1567 uf.data.assign((const char*)field->u.raw, field->size);
1568 Unknowns.push_back(uf);
1570 // return new pointer for next field
1571 return begin;
1574 void ServiceBook::Parse(const Data &data, size_t offset, unsigned int operation)
1576 const void *begin = 0;
1578 switch( operation )
1580 case SB_DBOP_GET_RECORDS:
1581 // using the new protocol
1582 // save the contact record ID
1583 throw std::logic_error("New ServiceBook: Not yet implemented");
1584 // RecordId = pack->u.db.u.calendar.uniqueId;
1585 // begin = &pack->u.db.u.calendar.field[0];
1586 break;
1588 case SB_DBOP_OLD_GET_RECORDS_REPLY:
1590 // using the old protocol
1591 // save the contact record ID
1592 MAKE_RECORD(const Barry::OldServiceBookRecord, sb, data, offset);
1593 RecordId = sb->uniqueId;
1594 begin = &sb->field[0];
1595 break;
1599 ParseCommonFields(*this, begin, data.GetData() + data.GetSize());
1603 // Build
1605 /// Build a raw protocol packet based on data in the class.
1607 void ServiceBook::Build(Data &data, size_t offset) const
1609 throw std::logic_error("ServiceBook::Build not yet implemented");
1611 data.Zap();
1613 // uploading always seems to use the old record
1614 size_t size = offset + OLD_SERVICE_BOOK_RECORD_HEADER_SIZE;
1615 unsigned char *pd = data.GetBuffer(size);
1616 MAKE_RECORD_PTR(Barry::OldServiceBookRecord, sbook, pd, offset);
1618 sbook->uniqueId = RecordId;
1619 sbook->unknown = 0;
1621 // output the type first
1622 BuildField(data, size, SBFC_APPT_TYPE_FLAG, Recurring ? '*' : 'a');
1624 // output all day event flag only if set
1625 if( AllDayEvent )
1626 BuildField(data, size, SBFC_ALLDAYEVENT_FLAG, (char)1);
1628 // cycle through the type table
1629 for( const FieldLink<ServiceBook> *b = ServiceBookFieldLinks;
1630 b->type != SBFC_END;
1631 b++ )
1633 if( b->strMember ) {
1634 const std::string &s = this->*(b->strMember);
1635 if( s.size() )
1636 BuildField(data, size, b->type, s);
1638 else if( b->timeMember ) {
1639 time_t t = this->*(b->timeMember);
1640 if( t > 0 )
1641 BuildField1900(data, size, b->type, t);
1645 // and finally save unknowns
1646 UnknownsType::const_iterator
1647 ub = Unknowns.begin(), ue = Unknowns.end();
1648 for( ; ub != ue; ub++ ) {
1649 BuildField(data, size, ub->type, ub->data);
1652 data.ReleaseBuffer(size);
1656 void ServiceBook::Clear()
1658 Unknowns.clear();
1659 Config.Clear();
1662 void ServiceBook::Dump(std::ostream &os) const
1664 os << "ServiceBook entry: 0x" << setbase(16) << RecordId << "\n";
1666 // cycle through the type table
1667 for( const FieldLink<ServiceBook> *b = ServiceBookFieldLinks;
1668 b->type != SBFC_END;
1669 b++ )
1671 if( b->strMember ) {
1672 const std::string &s = this->*(b->strMember);
1673 if( s.size() )
1674 os << " " << b->name << ": " << s << "\n";
1676 else if( b->timeMember ) {
1677 time_t t = this->*(b->timeMember);
1678 if( t > 0 )
1679 os << " " << b->name << ": " << ctime(&t);
1683 // special cases
1684 if( UniqueId.size() )
1685 os << " Unique ID: " << UniqueId << "\n";
1686 if( ContentId.size() )
1687 os << " Content ID: " << ContentId << "\n";
1688 if( BesDomain.size() )
1689 os << " (BES) Domain: " << BesDomain << "\n";
1691 os << Config;
1693 // print any unknowns
1694 os << Unknowns;
1698 } // namespace Barry
1701 #ifdef __TEST_MODE__
1703 #include <iostream>
1705 int main(int argc, char *argv[])
1707 if( argc < 2 ) {
1708 cerr << "Usage: test <datafile>" << endl;
1709 return 1;
1712 std::vector<Data> array;
1713 if( !LoadDataArray(argv[1], array) ) {
1714 cerr << "Unable to load file: " << argv[1] << endl;
1715 return 1;
1718 cout << "Loaded " << array.size() << " items" << endl;
1720 for( std::vector<Data>::iterator b = array.begin(), e = array.end();
1721 b != e; b++ )
1723 Data &d = *b;
1724 // cout << d << endl;
1725 if( d.GetSize() > 13 && d.GetData()[6] == 0x4f ) {
1726 Barry::Contact contact;
1727 contact.Parse(d, 13, d.GetData()[6]);
1728 cout << contact << endl;
1729 contact.DumpLdif(cout, "ou=People,dc=example,dc=com");
1731 else if( d.GetSize() > 13 && d.GetData()[6] == 0x44 ) {
1732 Barry::Calendar cal;
1733 cal.Parse(d, 13, d.GetData()[6]);
1734 cout << cal << endl;
1739 #endif