2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2001-2015 Match Grun and the Claws Mail team
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.
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.
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/>.
20 * Functions necessary to access vCard files. vCard files are used
21 * by GnomeCard for addressbook, and Netscape for sending business
22 * card information. Refer to http://www.imc.org/pdi/vcard-21.txt and
23 * RFC2426 for more information.
33 #include "addrcache.h"
34 #include "adbookbase.h"
37 #include "quoted-printable.h"
39 #define GNOMECARD_DIR ".gnome"
40 #define GNOMECARD_FILE "GnomeCard"
41 #define GNOMECARD_SECTION "[file]"
42 #define GNOMECARD_PARAM "open"
44 #define VCARD_TEST_LINES 200
47 * Create new cardfile object.
49 VCardFile
*vcard_create() {
51 cardFile
= g_new0( VCardFile
, 1 );
52 cardFile
->type
= ADBOOKTYPE_VCARD
;
53 cardFile
->addressCache
= addrcache_create();
54 cardFile
->retVal
= MGU_SUCCESS
;
56 cardFile
->file
= NULL
;
57 cardFile
->path
= NULL
;
58 cardFile
->bufptr
= cardFile
->buffer
;
65 void vcard_set_name( VCardFile
* cardFile
, const gchar
*value
) {
66 cm_return_if_fail( cardFile
!= NULL
);
67 addrcache_set_name( cardFile
->addressCache
, value
);
69 void vcard_set_file( VCardFile
* cardFile
, const gchar
*value
) {
70 cm_return_if_fail( cardFile
!= NULL
);
71 addrcache_refresh( cardFile
->addressCache
);
72 cardFile
->path
= mgu_replace_string( cardFile
->path
, value
);
73 g_strstrip( cardFile
->path
);
75 void vcard_set_accessed( VCardFile
*cardFile
, const gboolean value
) {
76 cm_return_if_fail( cardFile
!= NULL
);
77 cardFile
->addressCache
->accessFlag
= value
;
81 * Test whether file was modified since last access.
82 * Return: TRUE if file was modified.
84 gboolean
vcard_get_modified( VCardFile
*cardFile
) {
85 cm_return_val_if_fail( cardFile
!= NULL
, FALSE
);
86 cardFile
->addressCache
->modified
=
87 addrcache_check_file( cardFile
->addressCache
, cardFile
->path
);
88 return cardFile
->addressCache
->modified
;
90 gboolean
vcard_get_accessed( VCardFile
*cardFile
) {
91 cm_return_val_if_fail( cardFile
!= NULL
, FALSE
);
92 return cardFile
->addressCache
->accessFlag
;
96 * Test whether file was read.
97 * Return: TRUE if file was read.
99 gboolean
vcard_get_read_flag( VCardFile
*cardFile
) {
100 cm_return_val_if_fail( cardFile
!= NULL
, FALSE
);
101 return cardFile
->addressCache
->dataRead
;
105 * Return status code from last file operation.
106 * Return: Status code.
108 gint
vcard_get_status( VCardFile
*cardFile
) {
109 cm_return_val_if_fail( cardFile
!= NULL
, -1 );
110 return cardFile
->retVal
;
113 ItemFolder
*vcard_get_root_folder( VCardFile
*cardFile
) {
114 cm_return_val_if_fail( cardFile
!= NULL
, NULL
);
115 return addrcache_get_root_folder( cardFile
->addressCache
);
117 gchar
*vcard_get_name( VCardFile
*cardFile
) {
118 cm_return_val_if_fail( cardFile
!= NULL
, NULL
);
119 return addrcache_get_name( cardFile
->addressCache
);
123 * Create new cardfile object for specified file.
125 static VCardFile
*vcard_create_path( const gchar
*path
) {
127 cardFile
= vcard_create();
128 vcard_set_file(cardFile
, path
);
133 * Free up cardfile object by releasing internal memory.
135 void vcard_free( VCardFile
*cardFile
) {
136 cm_return_if_fail( cardFile
!= NULL
);
139 if( cardFile
->file
) fclose( cardFile
->file
);
142 addrcache_clear( cardFile
->addressCache
);
143 addrcache_free( cardFile
->addressCache
);
145 /* Free internal stuff */
146 g_free( cardFile
->path
);
149 cardFile
->file
= NULL
;
150 cardFile
->path
= NULL
;
151 cardFile
->bufptr
= NULL
;
153 cardFile
->type
= ADBOOKTYPE_NONE
;
154 cardFile
->addressCache
= NULL
;
155 cardFile
->retVal
= MGU_SUCCESS
;
157 /* Now release file object */
162 * Open file for read.
163 * return: TRUE if file opened successfully.
165 static gint
vcard_open_file( VCardFile
* cardFile
) {
166 cm_return_val_if_fail( cardFile
!= NULL
, -1 );
168 /* g_print( "Opening file\n" ); */
169 cardFile
->addressCache
->dataRead
= FALSE
;
170 if( cardFile
->path
) {
171 cardFile
->file
= g_fopen( cardFile
->path
, "rb" );
172 if( ! cardFile
->file
) {
173 /* g_printerr( "can't open %s\n", cardFile->path ); */
174 cardFile
->retVal
= MGU_OPEN_FILE
;
175 return cardFile
->retVal
;
179 /* g_printerr( "file not specified\n" ); */
180 cardFile
->retVal
= MGU_NO_FILE
;
181 return cardFile
->retVal
;
184 /* Setup a buffer area */
185 cardFile
->buffer
[0] = '\0';
186 cardFile
->bufptr
= cardFile
->buffer
;
187 cardFile
->retVal
= MGU_SUCCESS
;
188 return cardFile
->retVal
;
194 static void vcard_close_file( VCardFile
*cardFile
) {
195 cm_return_if_fail( cardFile
!= NULL
);
196 if( cardFile
->file
) fclose( cardFile
->file
);
197 cardFile
->file
= NULL
;
201 * Read line of text from file.
202 * Return: ptr to buffer where line starts.
204 static gchar
*vcard_read_line( VCardFile
*cardFile
) {
205 while( *cardFile
->bufptr
== '\n' || *cardFile
->bufptr
== '\0' ) {
206 if( fgets( cardFile
->buffer
, VCARDBUFSIZE
, cardFile
->file
) == NULL
)
208 g_strstrip( cardFile
->buffer
);
209 cardFile
->bufptr
= cardFile
->buffer
;
211 return cardFile
->bufptr
;
215 * Read line of text from file.
216 * Return: ptr to buffer where line starts.
218 static gchar
*vcard_get_line( VCardFile
*cardFile
) {
219 gchar buf
[ VCARDBUFSIZE
];
223 if (vcard_read_line( cardFile
) == NULL
) {
228 /* Copy into private buffer */
229 start
= cardFile
->bufptr
;
230 len
= strlen( start
);
232 strncpy( buf
, start
, len
);
235 cardFile
->bufptr
= end
+ 1;
237 /* Return a copy of buffer */
238 return g_strdup( buf
);
242 * Free linked lists of character strings.
244 static void vcard_free_lists( GSList
*listName
, GSList
*listAddr
, GSList
*listRem
, GSList
* listID
) {
245 mgu_free_list( listName
);
246 mgu_free_list( listAddr
);
247 mgu_free_list( listRem
);
248 mgu_free_list( listID
);
252 * Read quoted-printable text, which may span several lines into one long string.
253 * Param: cardFile - object.
254 * Param: tagvalue - will be placed into the linked list.
256 static gchar
*vcard_read_qp( VCardFile
*cardFile
, char *tagvalue
) {
257 GSList
*listQP
= NULL
;
259 gchar
*line
= tagvalue
;
261 listQP
= g_slist_append( listQP
, line
);
262 len
= strlen( line
) - 1;
263 if( line
[ len
] != '=' ) break;
265 line
= vcard_get_line( cardFile
);
268 /* Coalesce linked list into one long buffer. */
269 line
= mgu_list_coalesce( listQP
);
272 mgu_free_list( listQP
);
278 * Parse tag name from line buffer.
279 * Return: Buffer containing the tag name, or NULL if no delimiter char found.
281 static gchar
*vcard_get_tagname( char* line
, gchar dlm
) {
289 tag
= g_strndup( line
, len
+1 );
291 down
= g_utf8_strdown( tag
, -1 );
300 * Parse tag value from line buffer.
301 * Return: Buffer containing the tag value. Empty string is returned if
302 * no delimiter char found.
304 static gchar
*vcard_get_tagvalue( gchar
* line
, gchar dlm
) {
310 for( lptr
= line
; *lptr
; lptr
++ ) {
318 value
= g_strndup( start
, len
+1 );
321 /* Ensure that we get an empty string */
322 value
= g_strndup( "", 1 );
329 * Build an address list entry and append to list of address items.
331 static void vcard_build_items(
332 VCardFile
*cardFile
, GSList
*listName
, GSList
*listAddr
,
333 GSList
*listRem
, GSList
*listID
)
335 GSList
*nodeName
= listName
;
336 GSList
*nodeID
= listID
;
339 GSList
*nodeAddress
= listAddr
;
340 GSList
*nodeRemarks
= listRem
;
341 ItemPerson
*person
= addritem_create_item_person();
342 addritem_person_set_common_name( person
, nodeName
->data
);
343 while( nodeAddress
) {
344 str
= nodeAddress
->data
;
346 ItemEMail
*email
= addritem_create_item_email();
347 addritem_email_set_address( email
, str
);
349 str
= nodeRemarks
->data
;
351 if( g_utf8_collate( str
, "internet" ) != 0 ) {
353 addritem_email_set_remarks( email
, str
);
357 addrcache_id_email( cardFile
->addressCache
, email
);
358 addrcache_person_add_email( cardFile
->addressCache
, person
, email
);
360 nodeAddress
= g_slist_next( nodeAddress
);
361 nodeRemarks
= g_slist_next( nodeRemarks
);
363 if( person
->listEMail
) {
364 addrcache_id_person( cardFile
->addressCache
, person
);
365 addrcache_add_person( cardFile
->addressCache
, person
);
368 addritem_person_set_external_id( person
, str
);
372 addritem_free_item_person( person
);
374 nodeName
= g_slist_next( nodeName
);
375 nodeID
= g_slist_next( nodeID
);
379 /* Unescape characters in quoted-printable string. */
380 static gchar
*vcard_unescape_qp( gchar
*value
) {
388 qp_decode_const(res
, len
-1, value
);
389 if (!g_utf8_validate(res
, -1, NULL
)) {
390 gchar
*mybuf
= g_malloc(strlen(res
)*2 +1);
391 conv_localetodisp(mybuf
, strlen(res
)*2 +1, res
);
399 * Read file data into root folder.
400 * Note that one vCard can have multiple E-Mail addresses (MAIL tags);
401 * these are broken out into separate address items. An address item
402 * is generated for the person identified by FN tag and each EMAIL tag.
403 * If a sub-type is included in the EMAIL entry, this will be used as
404 * the Remarks member. Also note that it is possible for one vCard
405 * entry to have multiple FN tags; this might not make sense. However,
406 * it will generate duplicate address entries for each person listed.
408 static void vcard_read_file( VCardFile
*cardFile
) {
409 gchar
*tagtemp
= NULL
, *tagname
= NULL
, *tagvalue
= NULL
, *tagtype
= NULL
;
410 GSList
*listName
= NULL
, *listAddress
= NULL
, *listRemarks
= NULL
, *listID
= NULL
;
411 /* GSList *listQP = NULL; */
414 gchar
*line
= vcard_get_line( cardFile
);
415 if( line
== NULL
) break;
417 /* g_print( "%s\n", line ); */
420 tagtemp
= vcard_get_tagname( line
, VCARD_SEP_TAG
);
421 if( tagtemp
== NULL
) {
426 /* g_print( "\ttemp: %s\n", tagtemp ); */
427 tagvalue
= vcard_get_tagvalue( line
, VCARD_SEP_TAG
);
428 if( tagvalue
== NULL
) {
434 tagname
= vcard_get_tagname( tagtemp
, VCARD_SEP_TYPE
);
435 tagtype
= vcard_get_tagvalue( tagtemp
, VCARD_SEP_TYPE
);
436 if( tagname
== NULL
) {
441 /* g_print( "\tname: %s\n", tagname ); */
442 /* g_print( "\ttype: %s\n", tagtype ); */
443 /* g_print( "\tvalue: %s\n", tagvalue ); */
445 if( g_utf8_collate( tagtype
, VCARD_TYPE_QP
) == 0
446 || g_utf8_collate( tagtype
, VCARD_TYPE_E_QP
) == 0
447 || g_utf8_collate( tagtype
, VCARD_TYPE_CS_UTF8_E_QP
) == 0) {
449 /* Quoted-Printable: could span multiple lines */
450 tagvalue
= vcard_read_qp( cardFile
, tagvalue
);
451 tmp
= vcard_unescape_qp( tagvalue
);
454 /* g_print( "QUOTED-PRINTABLE !!! final\n>%s<\n", tagvalue ); */
457 if( g_utf8_collate( tagname
, VCARD_TAG_START
) == 0 &&
458 g_ascii_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
459 /* g_print( "start card\n" ); */
460 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
461 listName
= listAddress
= listRemarks
= listID
= NULL
;
463 if( g_utf8_collate( tagname
, VCARD_TAG_FULLNAME
) == 0 ) {
464 /* g_print( "- full name: %s\n", tagvalue ); */
465 listName
= g_slist_append( listName
, g_strdup( tagvalue
) );
467 if( g_utf8_collate( tagname
, VCARD_TAG_EMAIL
) == 0 ) {
468 /* g_print( "- address: %s\n", tagvalue ); */
469 listAddress
= g_slist_append( listAddress
, g_strdup( tagvalue
) );
470 listRemarks
= g_slist_append( listRemarks
, g_strdup( tagtype
) );
472 if( g_utf8_collate( tagname
, VCARD_TAG_UID
) == 0 ) {
473 /* g_print( "- id: %s\n", tagvalue ); */
474 listID
= g_slist_append( listID
, g_strdup( tagvalue
) );
476 if( g_utf8_collate( tagname
, VCARD_TAG_END
) == 0 &&
477 g_ascii_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
478 /* vCard is complete */
479 /* g_print( "end card\n--\n" ); */
480 /* vcard_dump_lists( listName, listAddress, listRemarks, listID, stdout ); */
481 vcard_build_items( cardFile
, listName
, listAddress
, listRemarks
, listID
);
482 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
483 listName
= listAddress
= listRemarks
= listID
= NULL
;
495 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
496 listName
= listAddress
= listRemarks
= listID
= NULL
;
499 /* ============================================================================================ */
501 * Read file into list. Main entry point
502 * Return: TRUE if file read successfully.
504 /* ============================================================================================ */
505 gint
vcard_read_data( VCardFile
*cardFile
) {
506 cm_return_val_if_fail( cardFile
!= NULL
, -1 );
508 cardFile
->retVal
= MGU_SUCCESS
;
509 cardFile
->addressCache
->accessFlag
= FALSE
;
510 if( addrcache_check_file( cardFile
->addressCache
, cardFile
->path
) ) {
511 addrcache_clear( cardFile
->addressCache
);
512 vcard_open_file( cardFile
);
513 if( cardFile
->retVal
== MGU_SUCCESS
) {
514 /* Read data into the list */
515 vcard_read_file( cardFile
);
516 vcard_close_file( cardFile
);
519 addrcache_mark_file( cardFile
->addressCache
, cardFile
->path
);
520 cardFile
->addressCache
->modified
= FALSE
;
521 cardFile
->addressCache
->dataRead
= TRUE
;
524 return cardFile
->retVal
;
528 * Return link list of persons.
530 GList
*vcard_get_list_person( VCardFile
*cardFile
) {
531 cm_return_val_if_fail( cardFile
!= NULL
, NULL
);
532 return addrcache_get_list_person( cardFile
->addressCache
);
536 * Return link list of folders. This is always NULL since there are
537 * no folders in GnomeCard.
540 GList
*vcard_get_list_folder( VCardFile
*cardFile
) {
541 cm_return_val_if_fail( cardFile
!= NULL
, NULL
);
546 * Return link list of all persons. Note that the list contains references
547 * to items. Do *NOT* attempt to use the addrbook_free_xxx() functions...
548 * this will destroy the addressbook data!
549 * Return: List of items, or NULL if none.
551 GList
*vcard_get_all_persons( VCardFile
*cardFile
) {
552 cm_return_val_if_fail( cardFile
!= NULL
, NULL
);
553 return addrcache_get_all_persons( cardFile
->addressCache
);
556 #define WORK_BUFLEN 1024
559 * Attempt to find a valid GnomeCard file.
560 * Return: Filename, or home directory if not found. Filename should
561 * be g_free() when done.
563 gchar
*vcard_find_gnomecard( void ) {
564 const gchar
*homedir
;
565 gchar buf
[ WORK_BUFLEN
];
566 gchar str
[ WORK_BUFLEN
+ 1 ];
571 homedir
= get_home_dir();
572 if( ! homedir
) return NULL
;
574 strncpy( str
, homedir
, WORK_BUFLEN
);
577 if( str
[ len
-1 ] != G_DIR_SEPARATOR
) {
578 str
[ len
] = G_DIR_SEPARATOR
;
582 strncat( str
, GNOMECARD_DIR
, WORK_BUFLEN
- strlen(str
) );
583 strncat( str
, G_DIR_SEPARATOR_S
, WORK_BUFLEN
- strlen(str
) );
584 strncat( str
, GNOMECARD_FILE
, WORK_BUFLEN
- strlen(str
) );
587 if( ( fp
= g_fopen( str
, "rb" ) ) != NULL
) {
588 /* Read configuration file */
589 lenlbl
= strlen( GNOMECARD_SECTION
);
590 while( fgets( buf
, sizeof( buf
), fp
) != NULL
) {
591 if( 0 == g_ascii_strncasecmp( buf
, GNOMECARD_SECTION
, lenlbl
) ) {
596 while( fgets( buf
, sizeof( buf
), fp
) != NULL
) {
598 if( buf
[0] == '[' ) break;
599 for( i
= 0; i
< lenlbl
; i
++ ) {
600 if( buf
[i
] == '=' ) {
601 if( 0 == g_ascii_strncasecmp( buf
, GNOMECARD_PARAM
, i
) ) {
602 fileSpec
= g_strdup( buf
+ i
+ 1 );
603 g_strstrip( fileSpec
);
611 if( fileSpec
== NULL
) {
612 /* Use the home directory */
614 fileSpec
= g_strdup( str
);
621 * Attempt to read file, testing for valid vCard format.
622 * Return: TRUE if file appears to be valid format.
624 gint
vcard_test_read_file( const gchar
*fileSpec
) {
626 gchar
*tagtemp
= NULL
, *tagname
= NULL
, *tagvalue
= NULL
, *tagtype
= NULL
, *line
;
630 if( ! fileSpec
) return MGU_NO_FILE
;
632 cardFile
= vcard_create_path( fileSpec
);
633 cardFile
->retVal
= MGU_SUCCESS
;
634 vcard_open_file( cardFile
);
635 if( cardFile
->retVal
== MGU_SUCCESS
) {
636 cardFile
->retVal
= MGU_BAD_FORMAT
;
638 lines
= VCARD_TEST_LINES
;
641 if( ( line
= vcard_get_line( cardFile
) ) == NULL
) break;
644 tagtemp
= vcard_get_tagname( line
, VCARD_SEP_TAG
);
645 if( tagtemp
== NULL
) {
650 tagvalue
= vcard_get_tagvalue( line
, VCARD_SEP_TAG
);
651 if( tagvalue
== NULL
) {
657 tagname
= vcard_get_tagname( tagtemp
, VCARD_SEP_TYPE
);
658 tagtype
= vcard_get_tagvalue( tagtemp
, VCARD_SEP_TYPE
);
659 if( tagname
== NULL
) {
664 if( g_utf8_collate( tagtype
, VCARD_TYPE_QP
) == 0 ) {
666 /* Quoted-Printable: could span multiple lines */
667 tagvalue
= vcard_read_qp( cardFile
, tagvalue
);
668 tmp
= vcard_unescape_qp( tagvalue
);
672 if( g_utf8_collate( tagname
, VCARD_TAG_START
) == 0 &&
673 g_ascii_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
676 if( g_utf8_collate( tagname
, VCARD_TAG_END
) == 0 &&
677 g_ascii_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
678 /* vCard is complete */
679 if( haveStart
) cardFile
->retVal
= MGU_SUCCESS
;
688 vcard_close_file( cardFile
);
690 retVal
= cardFile
->retVal
;
691 vcard_free( cardFile
);