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 | |
50 | using namespace KABC; |
51 | |
52 | /* generate LDIF stream */ |
53 | |
54 | bool 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 | |
64 | static 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 | |
77 | bool 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 | |
196 | bool 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 | |
273 | bool 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" ) ) { |
345 | addComment: |
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 | |