1/*
2 This file is part of libkabc.
3 Copyright (c) 2003 Helge Deller <deller@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21/*
22 Useful links:
23 - http://tldp.org/HOWTO/LDAP-Implementation-HOWTO/schemas.html
24 - http://www.faqs.org/rfcs/rfc2849.html
25
26 Not yet handled items:
27 - objectclass microsoftaddressbook
28 - info,
29 - initials,
30 - otherfacsimiletelephonenumber,
31 - otherpager,
32 - physicaldeliveryofficename,
33*/
34
35#include "ldifconverter.h"
36#include "vcardconverter.h"
37#include "address.h"
38#include "addressee.h"
39
40#include "ldif_p.h"
41
42#include <kdebug.h>
43#include <klocalizedstring.h>
44
45#include <QtCore/QRegExp>
46#include <QtCore/QStringList>
47#include <QtCore/QTextCodec>
48#include <QtCore/QTextStream>
49
50using namespace KABC;
51
52/* generate LDIF stream */
53
54bool LDIFConverter::addresseeToLDIF( const AddresseeList &addrList, QString &str )
55{
56 AddresseeList::ConstIterator it;
57 AddresseeList::ConstIterator end( addrList.constEnd() );
58 for ( it = addrList.constBegin(); it != end; ++it ) {
59 addresseeToLDIF( *it, str );
60 }
61 return true;
62}
63
64static void ldif_out( QTextStream &t, const QString &formatStr,
65 const QString &value )
66{
67 if ( value.isEmpty() ) {
68 return;
69 }
70
71 const QByteArray txt = Ldif::assembleLine( formatStr, value, 72 );
72
73 // write the string
74 t << QString::fromUtf8( txt ) << "\n";
75}
76
77bool LDIFConverter::addresseeToLDIF( const Addressee &addr, QString &str )
78{
79 if ( addr.isEmpty() ) {
80 return false;
81 }
82
83 QTextStream t( &str, QIODevice::WriteOnly|QIODevice::Append );
84 t.setCodec( QTextCodec::codecForName( "UTF-8" ) );
85
86 const Address homeAddr = addr.address( Address::Home );
87 const Address workAddr = addr.address( Address::Work );
88
89 ldif_out( t, QLatin1String( "dn" ), QString::fromLatin1( "cn=%1,mail=%2" ).
90 arg( addr.formattedName().simplified() ).
91 arg( addr.preferredEmail() ) );
92 ldif_out( t, QLatin1String( "givenname" ), addr.givenName() );
93 ldif_out( t, QLatin1String( "sn" ), addr.familyName() );
94 ldif_out( t, QLatin1String( "cn" ), addr.formattedName().simplified() );
95 ldif_out( t, QLatin1String( "uid" ), addr.uid() );
96 ldif_out( t, QLatin1String( "nickname" ), addr.nickName() );
97 ldif_out( t, QLatin1String( "xmozillanickname" ), addr.nickName() );
98 ldif_out( t, QLatin1String( "mozillanickname" ), addr.nickName() );
99
100 ldif_out( t, QLatin1String( "mail" ), addr.preferredEmail() );
101 if ( addr.emails().count() > 1 ) {
102 ldif_out( t, QLatin1String( "mozillasecondemail" ), addr.emails()[ 1 ] );
103 }
104//ldif_out( t, "mozilla_AIMScreenName: %1\n", "screen_name" );
105
106 ldif_out( t, QLatin1String( "telephonenumber" ),
107 addr.phoneNumber( PhoneNumber::Work ).number() );
108 ldif_out( t, QLatin1String( "facsimiletelephonenumber" ),
109 addr.phoneNumber( PhoneNumber::Fax ).number() );
110 ldif_out( t, QLatin1String( "homephone" ),
111 addr.phoneNumber( PhoneNumber::Home ).number() );
112 ldif_out( t, QLatin1String( "mobile" ),
113 addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 7
114 ldif_out( t, QLatin1String( "cellphone" ),
115 addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 4.x
116 ldif_out( t, QLatin1String( "pager" ),
117 addr.phoneNumber( PhoneNumber::Pager ).number() );
118 ldif_out( t, QLatin1String( "pagerphone" ),
119 addr.phoneNumber( PhoneNumber::Pager ).number() );
120
121 ldif_out( t, QLatin1String( "streethomeaddress" ), homeAddr.street() );
122 ldif_out( t, QLatin1String( "postalcode" ), workAddr.postalCode() );
123 ldif_out( t, QLatin1String( "postofficebox" ), workAddr.postOfficeBox() );
124
125 QStringList streets = homeAddr.street().split( QLatin1Char( '\n' ) );
126 const int numberOfStreets( streets.count() );
127 if ( numberOfStreets > 0 ) {
128 ldif_out( t, QLatin1String( "homepostaladdress" ), streets[ 0 ] ); // Netscape 7
129 }
130 if ( numberOfStreets > 1 ) {
131 ldif_out( t, QLatin1String( "mozillahomepostaladdress2" ), streets[ 1 ] ); // Netscape 7
132 }
133 ldif_out( t, QLatin1String( "mozillahomelocalityname" ), homeAddr.locality() ); // Netscape 7
134 ldif_out( t, QLatin1String( "mozillahomestate" ), homeAddr.region() );
135 ldif_out( t, QLatin1String( "mozillahomepostalcode" ), homeAddr.postalCode() );
136 ldif_out( t, QLatin1String( "mozillahomecountryname" ),
137 Address::ISOtoCountry( homeAddr.country() ) );
138 ldif_out( t, QLatin1String( "locality" ), workAddr.locality() );
139 ldif_out( t, QLatin1String( "streetaddress" ), workAddr.street() ); // Netscape 4.x
140
141 streets = workAddr.street().split( QLatin1Char( '\n' ) );
142 if ( streets.count() > 0 ) {
143 ldif_out( t, QLatin1String( "postaladdress" ), streets[ 0 ] );
144 }
145 if ( streets.count() > 1 ) {
146 ldif_out( t, QLatin1String( "mozillapostaladdress2" ), streets[ 1 ] );
147 }
148 ldif_out( t, QLatin1String( "countryname" ), Address::ISOtoCountry( workAddr.country() ) );
149 ldif_out( t, QLatin1String( "l" ), workAddr.locality() );
150 ldif_out( t, QLatin1String( "c" ), Address::ISOtoCountry( workAddr.country() ) );
151 ldif_out( t, QLatin1String( "st" ), workAddr.region() );
152
153 ldif_out( t, QLatin1String( "title" ), addr.title() );
154 ldif_out( t, QLatin1String( "vocation" ), addr.prefix() );
155 ldif_out( t, QLatin1String( "ou" ), addr.role() );
156 ldif_out( t, QLatin1String( "o" ), addr.organization() );
157 ldif_out( t, QLatin1String( "organization" ), addr.organization() );
158 ldif_out( t, QLatin1String( "organizationname" ), addr.organization() );
159
160 // Compatibility with older kabc versions.
161 if ( !addr.department().isEmpty() ) {
162 ldif_out( t, QLatin1String( "department" ), addr.department() );
163 } else {
164 ldif_out( t, QLatin1String( "department" ), addr.custom( QLatin1String( "KADDRESSBOOK" ),
165 QLatin1String( "X-Department" ) ) );
166 }
167
168 ldif_out( t, QLatin1String( "workurl" ), addr.url().prettyUrl() );
169 ldif_out( t, QLatin1String( "homeurl" ), addr.url().prettyUrl() );
170 ldif_out( t, QLatin1String( "mozillahomeurl" ), addr.url().prettyUrl() );
171
172 ldif_out( t, QLatin1String( "description" ), addr.note() );
173 if ( addr.revision().isValid() ) {
174 ldif_out( t, QLatin1String( "modifytimestamp" ), dateToVCardString( addr.revision() ) );
175 }
176
177 const QDateTime birthday = addr.birthday();
178 if ( birthday.isValid() ) {
179 const QDate date = birthday.date();
180 ldif_out( t, QLatin1String( "birthyear" ), QString::number( date.year() ) );
181 ldif_out( t, QLatin1String( "birthmonth" ), QString::number( date.month() ) );
182 ldif_out( t, QLatin1String( "birthday" ), QString::number( date.day() ) );
183 }
184
185 t << "objectclass: top\n";
186 t << "objectclass: person\n";
187 t << "objectclass: organizationalPerson\n";
188
189 t << "\n";
190
191 return true;
192}
193
194/* convert from LDIF stream */
195
196bool LDIFConverter::LDIFToAddressee( const QString &str, AddresseeList &addrList,
197 const QDateTime &dt )
198{
199 if ( str.isEmpty() ) {
200 return true;
201 }
202
203 bool endldif = false, end = false;
204 Ldif ldif;
205 Ldif::ParseValue ret;
206 Addressee a;
207 Address homeAddr, workAddr;
208 int birthday = -1;
209 int birthmonth = -1;
210 int birthyear = -1;
211
212 ldif.setLdif( str.toLatin1() );
213 QDateTime qdt = dt;
214 if ( !qdt.isValid() ) {
215 qdt = QDateTime::currentDateTime();
216 }
217 a.setRevision( qdt );
218 homeAddr = Address( Address::Home );
219 workAddr = Address( Address::Work );
220
221 do {
222 ret = ldif.nextItem();
223 switch ( ret ) {
224 case Ldif::Item:
225 {
226 QString fieldname = ldif.attr().toLower();
227 QString value = QString::fromUtf8( ldif.value(), ldif.value().size() );
228 evaluatePair( a, homeAddr, workAddr, fieldname, value, birthday, birthmonth, birthyear );
229 break;
230 }
231 case Ldif::EndEntry:
232 {
233 // if the new address is not empty, append it
234 QDateTime birthDate( QDate( birthyear, birthmonth, birthday ) );
235 if ( birthDate.isValid() ) {
236 a.setBirthday( birthDate );
237 }
238
239 if ( !a.formattedName().isEmpty() || !a.name().isEmpty() ||
240 !a.familyName().isEmpty() ) {
241 if ( !homeAddr.isEmpty() ) {
242 a.insertAddress( homeAddr );
243 }
244 if ( !workAddr.isEmpty() ) {
245 a.insertAddress( workAddr );
246 }
247 addrList.append( a );
248 }
249 a = Addressee();
250 a.setRevision( qdt );
251 homeAddr = Address( Address::Home );
252 workAddr = Address( Address::Work );
253 }
254 break;
255 case Ldif::MoreData:
256 {
257 if ( endldif ) {
258 end = true;
259 } else {
260 ldif.endLdif();
261 endldif = true;
262 break;
263 }
264 }
265 default:
266 break;
267 }
268 } while ( !end );
269
270 return true;
271}
272
273bool LDIFConverter::evaluatePair( Addressee &a, Address &homeAddr,
274 Address &workAddr,
275 QString &fieldname, QString &value,
276 int &birthday, int &birthmonth, int &birthyear )
277{
278 if ( fieldname == QLatin1String( "dn" ) ) { // ignore & return false!
279 return false;
280 }
281
282 if ( fieldname.startsWith( QLatin1Char( '#' ) ) ) {
283 return true;
284 }
285
286 if ( fieldname.isEmpty() && !a.note().isEmpty() ) {
287 // some LDIF export filters are borken and add additional
288 // comments on stand-alone lines. Just add them to the notes for now.
289 a.setNote( a.note() + QLatin1Char( '\n' ) + value );
290 return true;
291 }
292
293 if ( fieldname == QLatin1String( "givenname" ) ) {
294 a.setGivenName( value );
295 return true;
296 }
297
298 if ( fieldname == QLatin1String( "xmozillanickname" ) ||
299 fieldname == QLatin1String( "nickname" ) ||
300 fieldname == QLatin1String( "mozillanickname" ) ) {
301 a.setNickName( value );
302 return true;
303 }
304
305 if ( fieldname == QLatin1String( "sn" ) ) {
306 a.setFamilyName( value );
307 return true;
308 }
309
310 if ( fieldname == QLatin1String( "uid" ) ) {
311 a.setUid( value );
312 return true;
313 }
314 if ( fieldname == QLatin1String( "mail" ) ||
315 fieldname == QLatin1String( "mozillasecondemail" ) ) { // mozilla
316 if ( a.emails().indexOf( value ) == -1 ) {
317 a.insertEmail( value );
318 }
319 return true;
320 }
321
322 if ( fieldname == QLatin1String( "title" ) ) {
323 a.setTitle( value );
324 return true;
325 }
326
327 if ( fieldname == QLatin1String( "vocation" ) ) {
328 a.setPrefix( value );
329 return true;
330 }
331
332 if ( fieldname == QLatin1String( "cn" ) ) {
333 a.setFormattedName( value );
334 return true;
335 }
336
337 if ( fieldname == QLatin1String( "o" ) ||
338 fieldname == QLatin1String( "organization" ) || // Exchange
339 fieldname == QLatin1String( "organizationname" ) ) { // Exchange
340 a.setOrganization( value );
341 return true;
342 }
343
344 if ( fieldname == QLatin1String( "description" ) ) {
345addComment:
346 if ( !a.note().isEmpty() ) {
347 a.setNote( a.note() + QLatin1Char( '\n' ) );
348 }
349 a.setNote( a.note() + value );
350 return true;
351 }
352
353 if ( fieldname == QLatin1String( "custom1" ) ||
354 fieldname == QLatin1String( "custom2" ) ||
355 fieldname == QLatin1String( "custom3" ) ||
356 fieldname == QLatin1String( "custom4" ) ) {
357 goto addComment;
358 }
359
360 if ( fieldname == QLatin1String( "homeurl" ) ||
361 fieldname == QLatin1String( "workurl" ) ||
362 fieldname == QLatin1String( "mozillahomeurl" ) ) {
363 if ( a.url().isEmpty() ) {
364 a.setUrl( KUrl( value ) );
365 return true;
366 }
367 if ( a.url().prettyUrl() == KUrl( value ).prettyUrl() ) {
368 return true;
369 }
370 // TODO: current version of kabc only supports one URL.
371 // TODO: change this with KDE 4
372 }
373
374 if ( fieldname == QLatin1String( "homephone" ) ) {
375 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Home ) );
376 return true;
377 }
378
379 if ( fieldname == QLatin1String( "telephonenumber" ) ) {
380 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) );
381 return true;
382 }
383
384 if ( fieldname == QLatin1String( "mobile" ) ) { // mozilla/Netscape 7
385 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) );
386 return true;
387 }
388
389 if ( fieldname == QLatin1String( "cellphone" ) ) {
390 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) );
391 return true;
392 }
393
394 if ( fieldname == QLatin1String( "pager" ) || // mozilla
395 fieldname == QLatin1String( "pagerphone" ) ) { // mozilla
396 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Pager ) );
397 return true;
398 }
399
400 if ( fieldname == QLatin1String( "facsimiletelephonenumber" ) ) {
401 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Fax ) );
402 return true;
403 }
404
405 if ( fieldname == QLatin1String( "xmozillaanyphone" ) ) { // mozilla
406 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) );
407 return true;
408 }
409
410 if ( fieldname == QLatin1String( "streethomeaddress" ) ||
411 fieldname == QLatin1String( "mozillahomestreet" ) ) { // thunderbird
412 homeAddr.setStreet( value );
413 return true;
414 }
415
416 if ( fieldname == QLatin1String( "street" ) ||
417 fieldname == QLatin1String( "postaladdress" ) ) { // mozilla
418 workAddr.setStreet( value );
419 return true;
420 }
421
422 if ( fieldname == QLatin1String( "mozillapostaladdress2" ) ) { // mozilla
423 workAddr.setStreet( workAddr.street() + QLatin1String( "\n" ) + value );
424 return true;
425 }
426
427 if ( fieldname == QLatin1String( "postalcode" ) ) {
428 workAddr.setPostalCode( value );
429 return true;
430 }
431
432 if ( fieldname == QLatin1String( "postofficebox" ) ) {
433 workAddr.setPostOfficeBox( value );
434 return true;
435 }
436
437 if ( fieldname == QLatin1String( "homepostaladdress" ) ) { // Netscape 7
438 homeAddr.setStreet( value );
439 return true;
440 }
441
442 if ( fieldname == QLatin1String( "mozillahomepostaladdress2" ) ) { // mozilla
443 homeAddr.setStreet( homeAddr.street() + QLatin1String( "\n" ) + value );
444 return true;
445 }
446
447 if ( fieldname == QLatin1String( "mozillahomelocalityname" ) ) { // mozilla
448 homeAddr.setLocality( value );
449 return true;
450 }
451
452 if ( fieldname == QLatin1String( "mozillahomestate" ) ) { // mozilla
453 homeAddr.setRegion( value );
454 return true;
455 }
456
457 if ( fieldname == QLatin1String( "mozillahomepostalcode" ) ) { // mozilla
458 homeAddr.setPostalCode( value );
459 return true;
460 }
461
462 if ( fieldname == QLatin1String( "mozillahomecountryname" ) ) { // mozilla
463 if ( value.length() <= 2 ) {
464 value = Address::ISOtoCountry( value );
465 }
466 homeAddr.setCountry( value );
467 return true;
468 }
469
470 if ( fieldname == QLatin1String( "locality" ) ) {
471 workAddr.setLocality( value );
472 return true;
473 }
474
475 if ( fieldname == QLatin1String( "streetaddress" ) ) { // Netscape 4.x
476 workAddr.setStreet( value );
477 return true;
478 }
479
480 if ( fieldname == QLatin1String( "countryname" ) ||
481 fieldname == QLatin1String( "c" ) ) { // mozilla
482 if ( value.length() <= 2 ) {
483 value = Address::ISOtoCountry( value );
484 }
485 workAddr.setCountry( value );
486 return true;
487 }
488
489 if ( fieldname == QLatin1String( "l" ) ) { // mozilla
490 workAddr.setLocality( value );
491 return true;
492 }
493
494 if ( fieldname == QLatin1String( "st" ) ) {
495 workAddr.setRegion( value );
496 return true;
497 }
498
499 if ( fieldname == QLatin1String( "ou" ) ) {
500 a.setRole( value );
501 return true;
502 }
503
504 if ( fieldname == QLatin1String( "department" ) ) {
505 a.setDepartment( value );
506 return true;
507 }
508
509 if ( fieldname == QLatin1String( "member" ) ) {
510 // this is a mozilla list member (cn=xxx, mail=yyy)
511 QStringList list = value.split( QLatin1Char( ',' ) );
512 QString name, email;
513
514 QStringList::Iterator it;
515 for ( it = list.begin(); it != list.end(); ++it ) {
516 if ( ( *it ).startsWith( QLatin1String( "cn=" ) ) ) {
517 name = ( *it ).mid( 3 ).trimmed();
518 }
519 if ( ( *it ).startsWith( QLatin1String( "mail=" ) ) ) {
520 email = ( *it ).mid( 5 ).trimmed();
521 }
522 }
523 if ( !name.isEmpty() && !email.isEmpty() ) {
524 email = QLatin1String( " <" ) + email + QLatin1Char( '>' );
525 }
526 a.insertEmail( name + email );
527 a.insertCategory( i18n( "List of Emails" ) );
528 return true;
529 }
530
531 if ( fieldname == QLatin1String( "modifytimestamp" ) ) {
532 if ( value == QLatin1String( "0Z" ) ) { // ignore
533 return true;
534 }
535 QDateTime dt = VCardStringToDate( value );
536 if ( dt.isValid() ) {
537 a.setRevision( dt );
538 return true;
539 }
540 }
541
542 if ( fieldname == QLatin1String( "objectclass" ) ) { // ignore
543 return true;
544 }
545
546 if ( fieldname == QLatin1String( "birthyear" ) ) {
547 birthyear = value.toInt();
548 return true;
549 }
550 if ( fieldname == QLatin1String( "birthmonth" ) ) {
551 birthmonth = value.toInt();
552 return true;
553 }
554 if ( fieldname == QLatin1String( "birthday" ) ) {
555 birthday = value.toInt();
556 return true;
557 }
558
559 kWarning( 5700 ) << QString::fromLatin1( "LDIFConverter: Unknown field for '%1': '%2=%3'\n" ).
560 arg( a.formattedName() ).arg( fieldname ).arg( value );
561
562 return true;
563}
564