Fix comparison for RSS feed items without text
[claws.git] / src / exportldif.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2012 Match Grun and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 /*
21  * Export address book to LDIF file.
22  */
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <errno.h>
31 #include <time.h>
32 #include <string.h>
33 #include <glib.h>
34 #include <glib/gi18n.h>
35
36 #include "mgutils.h"
37 #include "utils.h"
38 #include "exportldif.h"
39 #include "xmlprops.h"
40 #include "ldif.h"
41 #include "file-utils.h"
42
43
44 #ifdef MKDIR_TAKES_ONE_ARG
45 #undef mkdir
46 #define mkdir(a,b) mkdir(a)
47 #endif
48
49 #define DFL_DIR_CLAWS_OUT  "claws-mail-out"
50 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
51
52 #define FMT_BUFSIZE           2048
53 #define XML_BUFSIZE           2048
54
55 /* Settings - properties */
56 #define EXML_PROPFILE_NAME    "exportldif.xml"
57 #define EXMLPROP_DIRECTORY    "directory"
58 #define EXMLPROP_FILE         "file"
59 #define EXMLPROP_SUFFIX       "suffix"
60 #define EXMLPROP_RDN_INDEX    "rdn"
61 #define EXMLPROP_USE_DN       "use-dn"
62 #define EXMLPROP_EXCL_EMAIL   "exclude-mail"
63
64 static gchar *_attrName_UID_   = "uid";
65 static gchar *_attrName_DName_ = "cn";
66 static gchar *_attrName_EMail_ = "mail";
67
68 /**
69  * Create initialized LDIF export control object.
70  * \return Initialized export control data.
71  */
72 ExportLdifCtl *exportldif_create( void ) {
73         ExportLdifCtl *ctl = g_new0( ExportLdifCtl, 1 );
74
75         ctl->path = NULL;
76         ctl->dirOutput = NULL;
77         ctl->fileLdif = NULL;
78         ctl->suffix = NULL;
79         ctl->rdnIndex = EXPORT_LDIF_ID_UID;
80         ctl->useDN = FALSE;
81         ctl->excludeEMail = TRUE;
82         ctl->retVal = MGU_SUCCESS;
83         ctl->rcCreate = 0;
84         ctl->settingsFile = g_strconcat(
85                 get_rc_dir(), G_DIR_SEPARATOR_S, EXML_PROPFILE_NAME, NULL );
86
87         return ctl;
88 }
89
90 /**
91  * Free up object by releasing internal memory.
92  * \return ctl Export control data.
93  */
94 void exportldif_free( ExportLdifCtl *ctl ) {
95         cm_return_if_fail( ctl != NULL );
96
97         g_free( ctl->path );
98         g_free( ctl->fileLdif );
99         g_free( ctl->dirOutput );
100         g_free( ctl->suffix );
101         g_free( ctl->settingsFile );
102
103         /* Clear pointers */
104         ctl->path = NULL;
105         ctl->dirOutput = NULL;
106         ctl->fileLdif = NULL;
107         ctl->suffix = NULL;
108         ctl->rdnIndex = EXPORT_LDIF_ID_UID;
109         ctl->useDN = FALSE;
110         ctl->excludeEMail = FALSE;
111         ctl->retVal = MGU_SUCCESS;
112         ctl->rcCreate = 0;
113
114         /* Now release object */
115         g_free( ctl );
116 }
117
118 /**
119  * Specify suffix to be used for creating DN entries.
120  * \param ctl   Export control data.
121  * \param value Suffix.
122  */
123 void exportldif_set_suffix( ExportLdifCtl *ctl, const char *value ) {
124         cm_return_if_fail( ctl != NULL );
125         ctl->suffix = mgu_replace_string( ctl->suffix, value );
126         g_strstrip( ctl->suffix );
127 }
128
129 /**
130  * Specify index of variable to be used for creating RDN entries.
131  * \param ctl   Export control data.
132  * \param value Index to variable, as follows:
133  * <ul>
134  * <li><code>EXPORT_LDIF_ID_UID</code> - Use Claws Mail UID.</li>
135  * <li><code>EXPORT_LDIF_ID_DNAME</code> - Use Claws Mail display name.</li>
136  * <li><code>EXPORT_LDIF_ID_EMAIL</code> - Use first Email address.</li>
137  * </ul>
138  */
139 void exportldif_set_rdn( ExportLdifCtl *ctl, const gint value ) {
140         cm_return_if_fail( ctl != NULL );
141         ctl->rdnIndex = value;
142 }
143
144 /**
145  * Specify that <code>DN</code> attribute, if present, should be used as the
146  * DN for the entry.
147  * \param ctl   Export control data.
148  * \param value <i>TRUE</i> if DN should be used.
149  */
150 void exportldif_set_use_dn( ExportLdifCtl *ctl, const gboolean value ) {
151         cm_return_if_fail( ctl != NULL );
152         ctl->useDN = value;
153 }
154
155 /**
156  * Specify that records without E-Mail addresses should be excluded.
157  * \param ctl   Export control data.
158  * \param value <i>TRUE</i> if records without E-Mail should be excluded.
159  */
160 void exportldif_set_exclude_email( ExportLdifCtl *ctl, const gboolean value ) {
161         cm_return_if_fail( ctl != NULL );
162         ctl->excludeEMail = value;
163 }
164
165 /**
166  * Format LDAP value name with no embedded commas.
167  * \param  value Data value to format.
168  * \return Formatted string, should be freed after use.
169  */
170 static gchar *exportldif_fmt_value( gchar *value ) {
171         gchar *dupval;
172         gchar *src;
173         gchar *dest;
174         gchar ch;
175
176         /* Duplicate incoming value */
177         dest = dupval = g_strdup( value );
178
179         /* Copy characters, ignoring commas */
180         src = value;
181         while( *src ) {
182                 ch = *src;
183                 if( ch != ',' ) {
184                         *dest = ch;
185                         dest++;
186                 }
187                 src++;
188         }
189         *dest = '\0';
190         return dupval;
191 }
192
193 /**
194  * Build DN for entry.
195  * \param  ctl    Export control data.
196  * \param  person Person to format.
197  * \return Formatted DN entry.
198  */
199 static gchar *exportldif_fmt_dn(
200                 ExportLdifCtl *ctl, const ItemPerson *person )
201 {
202         gchar buf[ FMT_BUFSIZE + 1 ];
203         gchar *retVal = NULL;
204         gchar *attr = NULL;
205         gchar *value = NULL;
206         gchar *dupval = NULL;
207
208         /* Process RDN */
209         *buf = '\0';
210         if( ctl->rdnIndex == EXPORT_LDIF_ID_UID ) {
211                 attr = _attrName_UID_;
212                 value = ADDRITEM_ID( person );
213         }
214         else if( ctl->rdnIndex == EXPORT_LDIF_ID_DNAME ) {
215                 attr = _attrName_DName_;
216                 value = ADDRITEM_NAME( person );
217                 dupval = exportldif_fmt_value( value );
218         }
219         else if( ctl->rdnIndex == EXPORT_LDIF_ID_EMAIL ) {
220                 GList *node;
221
222                 node = person->listEMail;
223                 if( node ) {
224                         ItemEMail *email = node->data;
225
226                         attr = _attrName_EMail_;
227                         value = email->address;
228                         dupval = exportldif_fmt_value( value );
229                 }
230         }
231
232         /* Format DN */
233         if( attr ) {
234                 if( value ) {
235                         if( strlen( value ) > 0 ) {
236                                 strncat( buf, attr, FMT_BUFSIZE - strlen(buf) );
237                                 strncat( buf, "=", FMT_BUFSIZE - strlen(buf) );
238                                 if( dupval ) {
239                                         /* Format and free duplicated value */
240                                         strncat( buf, dupval, FMT_BUFSIZE - strlen(buf) );
241                                         g_free( dupval );
242                                 }
243                                 else {
244                                         /* Use original value */
245                                         strncat( buf, value, FMT_BUFSIZE - strlen(buf) );
246                                 }
247
248                                 /* Append suffix */
249                                 if( ctl->suffix ) {
250                                         if( strlen( ctl->suffix ) > 0 ) {
251                                                 strncat( buf, ",", FMT_BUFSIZE - strlen(buf) );
252                                                 strncat( buf, ctl->suffix, FMT_BUFSIZE - strlen(buf) );
253                                         }
254                                 }
255
256                                 retVal = g_strdup( buf );
257                         }
258                 }
259         }
260         return retVal;
261 }
262
263 /**
264  * Find DN by searching attribute list.
265  * \param  ctl    Export control data.
266  * \param  person Person to format.
267  * \return Formatted DN entry, should be freed after use.
268  */
269 static gchar *exportldif_find_dn(
270                         ExportLdifCtl *ctl, const ItemPerson *person )
271 {
272         gchar *retVal = NULL;
273         const GList *node;
274
275         node = person->listAttrib;
276         while( node ) {
277                 UserAttribute *attrib = node->data;
278
279                 node = g_list_next( node );
280                 if( g_utf8_collate( attrib->name, LDIF_TAG_DN ) == 0 ) {
281                         retVal = g_strdup( attrib->value );
282                         break;
283                 }
284         }
285         return retVal;
286 }
287
288 /**
289  * Format E-Mail entries for person.
290  * \param  person Person to format.
291  * \param  stream Output stream.
292  * \return <i>TRUE</i> if entry formatted.
293  */
294 static gboolean exportldif_fmt_email( const ItemPerson *person, FILE *stream ) {
295         gboolean retVal = FALSE;
296         const GList *node;
297
298         node = person->listEMail;
299         while( node ) {
300                 ItemEMail *email = node->data;
301
302                 node = g_list_next( node );
303                 ldif_write_value( stream, LDIF_TAG_EMAIL, email->address );
304                 retVal = TRUE;
305         }
306         return retVal;
307 }
308
309 /**
310  * Test for E-Mail entries for person.
311  * \param  person Person to test.
312  * \return <i>TRUE</i> if person has E-Mail address.
313  */
314 static gboolean exportldif_test_email( const ItemPerson *person )
315 {
316         gboolean retVal = FALSE;
317         const GList *node;
318
319         node = person->listEMail;
320         while( node ) {
321                 ItemEMail *email = node->data;
322
323                 node = g_list_next( node );
324                 if( email->address ) {
325                         if( strlen( email->address ) > 0 ) {
326                                 retVal = TRUE;
327                                 break;
328                         }
329                 }
330                 retVal = TRUE;
331         }
332         return retVal;
333 }
334
335 /**
336  * Format other attributes for person.
337  * \param person ItemPerson.
338  * \param stream Output stream.
339  */
340 static void exportldif_fmt_other_attributes(ItemPerson* person, FILE* stream) {
341     UserAttribute* attr;
342     GList* attrList = NULL;
343     gchar* attrib;
344
345     if (! person)
346         return;
347     debug_print("cn: %s\n-----------------------------\n", ADDRITEM_NAME(person));
348     attrList = person->listAttrib;
349     while (attrList) {
350         attr = (UserAttribute *) attrList->data;
351         if (attr->uid) {
352             /* Native address book which does not conform to
353              * the LDAP schemas
354              */
355             attrib = g_strdup_printf("# %s", attr->name);
356         }
357         else {
358             attrib = g_strdup(attr->name);
359         }
360         debug_print("name: %s\nvalue: %s\n", attrib, attr->value);
361         ldif_write_value(stream, attrib, attr->value);
362         g_free(attrib);
363         attrList = g_list_next(attrList);
364     }
365     debug_print("-------------------------------\n");
366 }
367
368 /**
369  * Find persons displayName.
370  * \param person ItemPerson.
371  * \return displayName.
372  */
373 static gchar* exportldif_find_displayName(ItemPerson* person) {
374         gchar* displayName;
375
376         if (! person)
377             return NULL;
378         
379         if (person->nickName && strlen(person->nickName) > 0)
380                 displayName = g_strdup(person->nickName);
381         else
382                 displayName = g_strdup(ADDRITEM_NAME(person));
383         return displayName;
384 }
385
386 /**
387  * Format persons in an address book folder.
388  * \param  ctl    Export control data.
389  * \param  stream Output stream.
390  * \param  folder Folder to format.
391  * \return <i>TRUE</i> if no persons were formatted.
392  */
393 static gboolean exportldif_fmt_person(
394                 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
395 {
396         gboolean retVal = TRUE;
397         const GList *node;
398         gchar* sn = NULL;
399         gchar* displayName = NULL;
400
401         if( folder->listPerson == NULL ) return retVal;
402
403         node = folder->listPerson;
404         while( node ) {
405                 AddrItemObject *aio = node->data;
406                 node = g_list_next( node );
407
408                 if( aio && aio->type == ITEMTYPE_PERSON ) {
409                         ItemPerson *person = ( ItemPerson * ) aio;
410                         gboolean classPerson = FALSE;
411                         gboolean classInetP = FALSE;
412                         gchar *dn = NULL;
413
414                         /* Check for E-Mail */
415                         if( exportldif_test_email( person ) ) {
416                                 classInetP = TRUE;
417                         }
418                         else {
419                                 /* Bail if no E-Mail address */
420                                 if( ctl->excludeEMail ) continue;
421                         }
422
423                         /* Format DN */
424                         if( ctl->useDN ) {
425                                 dn = exportldif_find_dn( ctl, person );
426                         }
427                         if( dn == NULL ) {
428                                 dn = exportldif_fmt_dn( ctl, person );
429                         }
430                         if( dn == NULL ) continue;
431                         ldif_write_value( stream, LDIF_TAG_DN, dn );
432                         g_free( dn );
433
434                         /*
435                          * Test for schema requirements. This is a simple
436                          * test and does not trap all LDAP schema errors.
437                          * These can be detected when the LDIF file is
438                          * loaded into an LDAP server.
439                          */
440                         if( person->lastName ) {
441                                 if( strlen( person->lastName ) > 0 ) {
442                                         classPerson = TRUE;
443                                         classInetP = TRUE;
444                                 }
445                         }
446
447                         if( classPerson ) {
448                                 ldif_write_value( stream,
449                                         LDIF_TAG_OBJECTCLASS, LDIF_CLASS_PERSON );
450                         }
451                         if( classInetP ) {
452                                 ldif_write_value( stream,
453                                         LDIF_TAG_OBJECTCLASS, LDIF_CLASS_INET_PERSON );
454                         }
455
456                         /* Format person attributes */
457                         ldif_write_value(
458                                 stream, LDIF_TAG_COMMONNAME, ADDRITEM_NAME( person ) );
459                         sn = g_strdup(person->lastName);
460                         if (classPerson || classInetP) {
461                                 if(! sn || strcmp("", sn) == 0 || strcmp(" ", sn) == 0) {
462                                         g_free(sn);
463                                         sn = g_strdup("Some SN");
464                                 }
465                         }
466                         ldif_write_value(
467                                 stream, LDIF_TAG_LASTNAME, sn );
468                         g_free(sn);
469                         sn = NULL;
470                         ldif_write_value(
471                                 stream, LDIF_TAG_FIRSTNAME, person->firstName );
472
473                         if (! person->externalID)
474                                 displayName = exportldif_find_displayName(person);
475                         else
476                                 displayName = g_strdup(person->nickName);
477                         ldif_write_value(stream, LDIF_TAG_NICKNAME, displayName);
478                         g_free(displayName);
479                         displayName = NULL;
480
481                         /* Format E-Mail */
482                         exportldif_fmt_email( person, stream );
483                         
484                         /* Handle other attributes */
485                         exportldif_fmt_other_attributes(person, stream);
486
487                         /* End record */
488                         ldif_write_eor( stream );
489
490                         retVal = FALSE;
491                 }
492         }
493
494         return retVal;
495 }
496
497 /**
498  * Format an address book folder.
499  * \param  ctl    Export control data.
500  * \param  stream Output stream.
501  * \param  folder Folder to format.
502  * \return <i>TRUE</i> if no persons were formatted.
503  */
504 static void exportldif_fmt_folder(
505                 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
506 {
507         const GList *node;
508
509         /* Export entries in this folder */
510         exportldif_fmt_person( ctl, stream, folder );
511
512         /* Export entries in sub-folders */
513         node = folder->listFolder;
514         while( node ) {
515                 AddrItemObject *aio = node->data;
516
517                 node = g_list_next( node );
518                 if( aio && aio->type == ITEMTYPE_FOLDER ) {
519                         ItemFolder *subFolder = ( ItemFolder * ) aio;
520                         exportldif_fmt_folder( ctl, stream, subFolder );
521                 }
522         }
523 }
524
525 /**
526  * Export address book to LDIF file.
527  * \param  ctl   Export control data.
528  * \param  cache Address book/data source cache.
529  * \return Status.
530  */
531 void exportldif_process( ExportLdifCtl *ctl, AddressCache *cache )
532 {
533         ItemFolder *rootFolder;
534         FILE *ldifFile;
535
536         ldifFile = claws_fopen( ctl->path, "wb" );
537         if( ! ldifFile ) {
538                 /* Cannot open file */
539                 ctl->retVal = MGU_OPEN_FILE;
540                 return;
541         }
542
543         rootFolder = cache->rootFolder;
544         exportldif_fmt_folder( ctl, ldifFile, rootFolder );
545         claws_safe_fclose( ldifFile );
546         ctl->retVal = MGU_SUCCESS;
547 }
548
549 /**
550  * Build full export file specification.
551  * \param ctl  Export control data.
552  */
553 static void exportldif_build_filespec( ExportLdifCtl *ctl ) {
554         gchar *fileSpec;
555
556         fileSpec = g_strconcat(
557                 ctl->dirOutput, G_DIR_SEPARATOR_S, ctl->fileLdif, NULL );
558         ctl->path = mgu_replace_string( ctl->path, fileSpec );
559         g_free( fileSpec );
560 }
561
562 /**
563  * Parse directory and filename from full export file specification.
564  * \param ctl      Export control data.
565  * \param fileSpec File spec.
566  */
567 void exportldif_parse_filespec( ExportLdifCtl *ctl, gchar *fileSpec ) {
568         gchar *t;
569         gchar *base = g_path_get_basename(fileSpec);
570
571         ctl->fileLdif =
572                 mgu_replace_string( ctl->fileLdif, base );
573         g_free(base);
574         t = g_path_get_dirname( fileSpec );
575         ctl->dirOutput = mgu_replace_string( ctl->dirOutput, t );
576         g_free( t );
577         ctl->path = mgu_replace_string( ctl->path, fileSpec );
578 }
579
580 /**
581  * Create output directory.
582  * \param  ctl Export control data.
583  * \return TRUE if directory created.
584  */
585 gboolean exportldif_create_dir( ExportLdifCtl *ctl ) {
586         gboolean retVal = FALSE;
587
588         ctl->rcCreate = 0;
589         if( mkdir( ctl->dirOutput, S_IRWXU ) == 0 ) {
590                 retVal = TRUE;
591         }
592         else {
593                 ctl->rcCreate = errno;
594         }
595         return retVal;
596 }
597
598 /**
599  * Retrieve create directory error message.
600  * \param  ctl Export control data.
601  * \return Message.
602  */
603 gchar *exportldif_get_create_msg( ExportLdifCtl *ctl ) {
604         gchar *msg;
605
606         if( ctl->rcCreate == EEXIST ) {
607                 msg = _( "Name already exists but is not a directory." );
608         }
609         else if( ctl->rcCreate == EACCES ) {
610                 msg = _( "No permissions to create directory." );
611         }
612         else if( ctl->rcCreate == ENAMETOOLONG ) {
613                 msg = _( "Name is too long." );
614         }
615         else {
616                 msg = _( "Not specified." );
617         }
618         return msg;
619 }
620
621 /**
622  * Set default values.
623  * \param  ctl Export control data.
624  */
625 static void exportldif_default_values( ExportLdifCtl *ctl ) {
626         gchar *str;
627
628         str = g_strconcat(
629                 get_home_dir(), G_DIR_SEPARATOR_S,
630                 DFL_DIR_CLAWS_OUT, NULL );
631
632         ctl->dirOutput = mgu_replace_string( ctl->dirOutput, str );
633         g_free( str );
634
635         ctl->fileLdif =
636                 mgu_replace_string( ctl->fileLdif, DFL_FILE_CLAWS_OUT );
637         ctl->suffix = mgu_replace_string( ctl->suffix, "" );
638
639         ctl->rdnIndex = EXPORT_LDIF_ID_UID;
640         ctl->useDN = FALSE;
641         ctl->retVal = MGU_SUCCESS;
642 }
643
644 /**
645  * Load settings from XML properties file.
646  * \param  ctl Export control data.
647  */
648 void exportldif_load_settings( ExportLdifCtl *ctl ) {
649         XmlProperty *props;
650         gint rc;
651         gchar buf[ XML_BUFSIZE ];
652
653         props = xmlprops_create();
654         xmlprops_set_path( props, ctl->settingsFile );
655         rc = xmlprops_load_file( props );
656         if( rc == 0 ) {
657                 /* Read settings */
658                 *buf = '\0';
659                 xmlprops_get_property_s( props, EXMLPROP_DIRECTORY, buf );
660                 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, buf );
661
662                 *buf = '\0';
663                 xmlprops_get_property_s( props, EXMLPROP_FILE, buf );
664                 ctl->fileLdif = mgu_replace_string( ctl->fileLdif, buf );
665
666                 *buf = '\0';
667                 xmlprops_get_property_s( props, EXMLPROP_SUFFIX, buf );
668                 ctl->suffix = mgu_replace_string( ctl->suffix, buf );
669
670                 ctl->rdnIndex =
671                         xmlprops_get_property_i( props, EXMLPROP_RDN_INDEX );
672                 ctl->useDN =
673                         xmlprops_get_property_b( props, EXMLPROP_USE_DN );
674                 ctl->excludeEMail =
675                         xmlprops_get_property_b( props, EXMLPROP_EXCL_EMAIL );
676         }
677         else {
678                 /* Set default values */
679                 exportldif_default_values( ctl );
680         }
681         exportldif_build_filespec( ctl );
682         /* exportldif_print( ctl, stdout ); */
683
684         xmlprops_free( props );
685 }
686
687 /**
688  * Save settings to XML properties file.
689  * \param  ctl Export control data.
690  */
691 void exportldif_save_settings( ExportLdifCtl *ctl ) {
692         XmlProperty *props;
693
694         props = xmlprops_create();
695         xmlprops_set_path( props, ctl->settingsFile );
696
697         xmlprops_set_property( props, EXMLPROP_DIRECTORY, ctl->dirOutput );
698         xmlprops_set_property( props, EXMLPROP_FILE, ctl->fileLdif );
699         xmlprops_set_property( props, EXMLPROP_SUFFIX, ctl->suffix );
700         xmlprops_set_property_i( props, EXMLPROP_RDN_INDEX, ctl->rdnIndex );
701         xmlprops_set_property_b( props, EXMLPROP_USE_DN, ctl->useDN );
702         xmlprops_set_property_b( props, EXMLPROP_EXCL_EMAIL, ctl->excludeEMail );
703         if (xmlprops_save_file( props ) != MGU_SUCCESS)
704                 g_warning("can't save settings");
705         xmlprops_free( props );
706 }
707
708 /*
709  * ============================================================================
710  * End of Source.
711  * ============================================================================
712  */
713
714