2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2001 Match Grun
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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Functions necessary to access VCard files. VCard files are used
22 * by GnomeCard for addressbook, and Netscape for sending business
23 * card information. Refer to RFC2426 for more information.
32 #define GNOMECARD_DIR ".gnome"
33 #define GNOMECARD_FILE "GnomeCard"
34 #define GNOMECARD_SECTION "[file]"
35 #define GNOMECARD_PARAM "open"
37 #define VCARD_TEST_LINES 200
40 * Specify name to be used.
42 void vcard_set_name( VCardFile
* cardFile
, const gchar
*name
) {
44 if( cardFile
->name
) g_free( cardFile
->name
);
45 if( name
) cardFile
->name
= g_strdup( name
);
46 g_strstrip( cardFile
->name
);
50 * Specify file to be used.
52 void vcard_set_file( VCardFile
* cardFile
, const gchar
*path
) {
53 mgu_refresh_cache( cardFile
->addressCache
);
56 if( cardFile
->path
) g_free( cardFile
->path
);
57 if( path
) cardFile
->path
= g_strdup( path
);
58 g_strstrip( cardFile
->path
);
62 * Create new cardfile object.
64 VCardFile
*vcard_create() {
66 cardFile
= g_new( VCardFile
, 1 );
67 cardFile
->name
= NULL
;
68 cardFile
->path
= NULL
;
69 cardFile
->file
= NULL
;
70 cardFile
->bufptr
= cardFile
->buffer
;
71 cardFile
->addressCache
= mgu_create_cache();
72 cardFile
->retVal
= MGU_SUCCESS
;
77 * Refresh internal variables to force a file read.
79 void vcard_force_refresh( VCardFile
*cardFile
) {
80 mgu_refresh_cache( cardFile
->addressCache
);
84 * Create new cardfile object for specified file.
86 VCardFile
*vcard_create_path( const gchar
*path
) {
88 cardFile
= vcard_create();
89 vcard_set_file(cardFile
, path
);
94 * Free up cardfile object by releasing internal memory.
96 void vcard_free( VCardFile
*cardFile
) {
97 g_return_if_fail( cardFile
!= NULL
);
99 // fprintf( stdout, "freeing... VCardFile\n" );
102 if( cardFile
->file
) fclose( cardFile
->file
);
104 /* Free internal stuff */
105 g_free( cardFile
->name
);
106 g_free( cardFile
->path
);
109 mgu_clear_cache( cardFile
->addressCache
);
110 mgu_free_cache( cardFile
->addressCache
);
113 cardFile
->file
= NULL
;
114 cardFile
->name
= NULL
;
115 cardFile
->path
= NULL
;
116 cardFile
->addressCache
= NULL
;
117 cardFile
->retVal
= MGU_SUCCESS
;
119 /* Now release file object */
122 // fprintf( stdout, "freeing... VCardFile done\n" );
127 * Display object to specified stream.
129 void vcard_print_file( VCardFile
*cardFile
, FILE *stream
) {
131 g_return_if_fail( cardFile
!= NULL
);
132 fprintf( stream
, "VCardFile:\n" );
133 fprintf( stream
, " name: '%s'\n", cardFile
->name
);
134 fprintf( stream
, "file spec: '%s'\n", cardFile
->path
);
135 fprintf( stream
, " ret val: %d\n", cardFile
->retVal
);
136 mgu_print_cache( cardFile
->addressCache
, stream
);
140 * Open file for read.
141 * return: TRUE if file opened successfully.
143 gint
vcard_open_file( VCardFile
* cardFile
) {
144 g_return_if_fail( cardFile
!= NULL
);
146 // fprintf( stdout, "Opening file\n" );
147 cardFile
->addressCache
->dataRead
= FALSE
;
148 if( cardFile
->path
) {
149 cardFile
->file
= fopen( cardFile
->path
, "r" );
150 if( ! cardFile
->file
) {
151 // fprintf( stderr, "can't open %s\n", cardFile->path );
152 cardFile
->retVal
= MGU_OPEN_FILE
;
153 return cardFile
->retVal
;
157 // fprintf( stderr, "file not specified\n" );
158 cardFile
->retVal
= MGU_NO_FILE
;
159 return cardFile
->retVal
;
162 /* Setup a buffer area */
163 cardFile
->buffer
[0] = '\0';
164 cardFile
->bufptr
= cardFile
->buffer
;
165 cardFile
->retVal
= MGU_SUCCESS
;
166 return cardFile
->retVal
;
172 void vcard_close_file( VCardFile
*cardFile
) {
173 g_return_if_fail( cardFile
!= NULL
);
174 if( cardFile
->file
) fclose( cardFile
->file
);
175 cardFile
->file
= NULL
;
179 * Read line of text from file.
180 * Return: ptr to buffer where line starts.
182 gchar
*vcard_read_line( VCardFile
*cardFile
) {
183 while( *cardFile
->bufptr
== '\n' || *cardFile
->bufptr
== '\0' ) {
184 if( fgets( cardFile
->buffer
, VCARDBUFSIZE
, cardFile
->file
) == NULL
)
186 g_strstrip( cardFile
->buffer
);
187 cardFile
->bufptr
= cardFile
->buffer
;
189 return cardFile
->bufptr
;
193 * Read line of text from file.
194 * Return: ptr to buffer where line starts.
196 gchar
*vcard_get_line( VCardFile
*cardFile
) {
197 gchar buf
[ VCARDBUFSIZE
];
201 if (vcard_read_line( cardFile
) == NULL
) {
206 /* Copy into private buffer */
207 start
= cardFile
->bufptr
;
208 len
= strlen( start
);
210 strncpy( buf
, start
, len
);
213 cardFile
->bufptr
= end
+ 1;
215 /* Return a copy of buffer */
216 return g_strdup( buf
);
220 * Free linked lists of character strings.
222 void vcard_free_lists( GSList
*listName
, GSList
*listAddr
, GSList
*listRem
, GSList
* listID
) {
223 mgu_free_list( listName
);
224 mgu_free_list( listAddr
);
225 mgu_free_list( listRem
);
226 mgu_free_list( listID
);
230 * Read quoted-printable text, which may span several lines into one long string.
231 * Param: cardFile - object.
232 * Param: tagvalue - will be placed into the linked list.
234 gchar
*vcard_read_qp( VCardFile
*cardFile
, char *tagvalue
) {
235 GSList
*listQP
= NULL
;
237 gchar
*line
= tagvalue
;
239 listQP
= g_slist_append( listQP
, line
);
240 len
= strlen( line
) - 1;
242 if( line
[ len
] != '=' ) break;
245 line
= vcard_get_line( cardFile
);
248 // Coalesce linked list into one long buffer.
249 line
= mgu_list_coalesce( listQP
);
252 mgu_free_list( listQP
);
258 * Parse tag name from line buffer.
259 * Return: Buffer containing the tag name, or NULL if no delimiter char found.
261 gchar
*vcard_get_tagname( char* line
, gchar dlm
) {
268 tag
= g_strndup( line
, len
+1 );
278 * Parse tag value from line buffer.
279 * Return: Buffer containing the tag value. Empty string is returned if
280 * no delimiter char found.
282 gchar
*vcard_get_tagvalue( gchar
* line
, gchar dlm
) {
288 for( lptr
= line
; *lptr
; lptr
++ ) {
296 value
= g_strndup( start
, len
+1 );
299 // Ensure that we get an empty string
300 value
= g_strndup( "", 1 );
307 * Dump linked lists of character strings (for debug).
309 void vcard_dump_lists( GSList
*listName
, GSList
*listAddr
, GSList
*listRem
, GSList
*listID
, FILE *stream
) {
310 fprintf( stream
, "dump name\n" );
311 fprintf( stream
, "------------\n" );
312 mgu_print_list( listName
, stdout
);
313 fprintf( stream
, "dump address\n" );
314 fprintf( stream
, "------------\n" );
315 mgu_print_list( listAddr
, stdout
);
316 fprintf( stream
, "dump remarks\n" );
317 fprintf( stdout
, "------------\n" );
318 mgu_print_list( listRem
, stdout
);
319 fprintf( stream
, "dump id\n" );
320 fprintf( stdout
, "------------\n" );
321 mgu_print_list( listID
, stdout
);
325 * Build an address list entry and append to list of address items.
327 void vcard_build_items( VCardFile
*cardFile
, GSList
*listName
, GSList
*listAddr
, GSList
*listRem
, GSList
*listID
) {
328 AddressItem
*addrItem
= NULL
;
329 GSList
*nodeName
= listName
;
330 GSList
*nodeID
= listID
;
332 GSList
*nodeAddress
= listAddr
;
333 GSList
*nodeRemarks
= listRem
;
334 while( nodeAddress
) {
335 addrItem
= mgu_create_address();
336 addrItem
->name
= g_strdup( nodeName
->data
);
337 addrItem
->address
= g_strdup( nodeAddress
->data
);
339 if( nodeRemarks
->data
) {
340 if( g_strcasecmp( nodeRemarks
->data
, "internet" ) == 0 ) {
341 // Trivially exclude this one (appears for most records)
342 addrItem
->remarks
= g_strdup( "" );
345 addrItem
->remarks
= g_strdup( nodeRemarks
->data
);
349 addrItem
->remarks
= g_strdup( "" );
353 addrItem
->remarks
= g_strdup( "" );
358 addrItem->externalID = g_strdup( nodeID->data );
361 addrItem->externalID = g_strdup( "" );
365 addrItem->externalID = g_strdup( "" );
368 mgu_add_cache( cardFile
->addressCache
, addrItem
);
370 nodeAddress
= g_slist_next( nodeAddress
);
371 nodeRemarks
= g_slist_next( nodeRemarks
);
373 nodeName
= g_slist_next( nodeName
);
374 nodeID
= g_slist_next( nodeID
);
379 // Unescape characters in quoted-printable string.
380 void vcard_unescape_qp( gchar
*value
) {
381 gchar
*ptr
, *src
, *dest
;
392 if( ch
> '0' && ch
< '8' ) v
= ch
- '0';
397 if( ch
> '\x60' ) ch
-= '\x20';
398 if( ch
> '0' && ch
< ' ' ) d
= ch
- '0';
401 if( d
> -1 && d
< 16 ) {
408 // Replace = with char and move down in buffer
422 * Read file into cache.
423 * Note that one VCard can have multiple E-Mail addresses (MAIL tags);
424 * these are broken out into separate address items. An address item
425 * is generated for the person identified by FN tag and each EMAIL tag.
426 * If a sub-type is included in the EMAIL entry, this will be used as
427 * the Remarks member. Also note that it is possible for one VCard
428 * entry to have multiple FN tags; this might not make sense. However,
429 * it will generate duplicate address entries for each person listed.
431 void vcard_read_cache( VCardFile
*cardFile
) {
432 gchar
*tagtemp
= NULL
, *tagname
= NULL
, *tagvalue
= NULL
, *tagtype
= NULL
, *tagrest
= NULL
;
433 GSList
*listName
= NULL
, *listAddress
= NULL
, *listRemarks
= NULL
, *listID
= NULL
;
434 GSList
*listQP
= NULL
;
437 gchar
*line
= vcard_get_line( cardFile
);
438 if( line
== NULL
) break;
440 // fprintf( stdout, "%s\n", line );
443 tagtemp
= vcard_get_tagname( line
, VCARD_SEP_TAG
);
445 // fprintf( stdout, "\ttemp: %s\n", tagtemp );
446 tagvalue
= vcard_get_tagvalue( line
, VCARD_SEP_TAG
);
447 tagname
= vcard_get_tagname( tagtemp
, VCARD_SEP_TYPE
);
448 tagtype
= vcard_get_tagvalue( tagtemp
, VCARD_SEP_TYPE
);
449 if( tagname
== NULL
) {
454 // fprintf( stdout, "\tname: %s\n", tagname );
455 // fprintf( stdout, "\ttype: %s\n", tagtype );
456 // fprintf( stdout, "\tvalue: %s\n", tagvalue );
459 if( g_strcasecmp( tagtype
, VCARD_TYPE_QP
) == 0 ) {
460 // Quoted-Printable: could span multiple lines
461 tagvalue
= vcard_read_qp( cardFile
, tagvalue
);
462 vcard_unescape_qp( tagvalue
);
463 // fprintf( stdout, "QUOTED-PRINTABLE !!! final\n>%s<\n", tagvalue );
466 if( g_strcasecmp( tagname
, VCARD_TAG_START
) == 0 && g_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
467 // fprintf( stdout, "start card\n" );
468 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
469 listName
= listAddress
= listRemarks
= listID
= NULL
;
471 if( g_strcasecmp( tagname
, VCARD_TAG_FULLNAME
) == 0 ) {
472 // fprintf( stdout, "- full name: %s\n", tagvalue );
473 listName
= g_slist_append( listName
, g_strdup( tagvalue
) );
475 if( g_strcasecmp( tagname
, VCARD_TAG_EMAIL
) == 0 ) {
476 // fprintf( stdout, "- address: %s\n", tagvalue );
477 listAddress
= g_slist_append( listAddress
, g_strdup( tagvalue
) );
478 listRemarks
= g_slist_append( listRemarks
, g_strdup( tagtype
) );
480 if( g_strcasecmp( tagname
, VCARD_TAG_UID
) == 0 ) {
481 // fprintf( stdout, "- id: %s\n", tagvalue );
482 listID
= g_slist_append( listID
, g_strdup( tagvalue
) );
484 if( g_strcasecmp( tagname
, VCARD_TAG_END
) == 0 && g_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
486 // fprintf( stdout, "end card\n--\n" );
487 // vcard_dump_lists( listName, listAddress, listRemarks, listID, stdout );
488 vcard_build_items( cardFile
, listName
, listAddress
, listRemarks
, listID
);
489 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
490 listName
= listAddress
= listRemarks
= listID
= NULL
;
500 vcard_free_lists( listName
, listAddress
, listRemarks
, listID
);
501 listName
= listAddress
= listRemarks
= listID
= NULL
;
504 // ============================================================================================
506 * Read file into list. Main entry point
507 * Return: TRUE if file read successfully.
509 // ============================================================================================
510 gint
vcard_read_data( VCardFile
*cardFile
) {
511 g_return_if_fail( cardFile
!= NULL
);
512 cardFile
->retVal
= MGU_SUCCESS
;
513 if( mgu_check_file( cardFile
->addressCache
, cardFile
->path
) ) {
514 mgu_clear_cache( cardFile
->addressCache
);
515 vcard_open_file( cardFile
);
516 if( cardFile
->retVal
== MGU_SUCCESS
) {
517 // Read data into the list
518 vcard_read_cache( cardFile
);
519 vcard_close_file( cardFile
);
522 mgu_mark_cache( cardFile
->addressCache
, cardFile
->path
);
523 cardFile
->addressCache
->modified
= FALSE
;
524 cardFile
->addressCache
->dataRead
= TRUE
;
527 return cardFile
->retVal
;
531 * Return link list of address items.
532 * Return: TRUE if file read successfully.
534 GList
*vcard_get_address_list( VCardFile
*cardFile
) {
535 g_return_if_fail( cardFile
!= NULL
);
536 return cardFile
->addressCache
->addressList
;
540 * Validate that all parameters specified.
541 * Return: TRUE if data is good.
543 gboolean
vcard_validate( const VCardFile
*cardFile
) {
545 g_return_if_fail( cardFile
!= NULL
);
548 if( cardFile
->path
) {
549 if( strlen( cardFile
->path
) < 1 ) retVal
= FALSE
;
554 if( cardFile
->name
) {
555 if( strlen( cardFile
->name
) < 1 ) retVal
= FALSE
;
563 #define WORK_BUFLEN 1024
566 * Attempt to find a valid GnomeCard file.
567 * Return: Filename, or home directory if not found. Filename should
568 * be g_free() when done.
570 gchar
*vcard_find_gnomecard( void ) {
572 gchar buf
[ WORK_BUFLEN
];
573 gchar str
[ WORK_BUFLEN
];
578 homedir
= g_get_home_dir();
579 if( ! homedir
) return NULL
;
581 strcpy( str
, homedir
);
584 if( str
[ len
-1 ] != G_DIR_SEPARATOR
) {
585 str
[ len
] = G_DIR_SEPARATOR
;
589 strcat( str
, GNOMECARD_DIR
);
590 strcat( str
, G_DIR_SEPARATOR_S
);
591 strcat( str
, GNOMECARD_FILE
);
594 if( ( fp
= fopen( str
, "r" ) ) != NULL
) {
595 // Read configuration file
596 lenlbl
= strlen( GNOMECARD_SECTION
);
597 while( fgets( buf
, sizeof( buf
), fp
) != NULL
) {
598 if( 0 == g_strncasecmp( buf
, GNOMECARD_SECTION
, lenlbl
) ) {
603 while( fgets( buf
, sizeof( buf
), fp
) != NULL
) {
605 if( buf
[0] == '[' ) break;
606 for( i
= 0; i
< lenlbl
; i
++ ) {
607 if( buf
[i
] == '=' ) {
608 if( 0 == g_strncasecmp( buf
, GNOMECARD_PARAM
, i
) ) {
609 fileSpec
= g_strdup( buf
+ i
+ 1 );
610 g_strstrip( fileSpec
);
618 if( fileSpec
== NULL
) {
619 // Use the home directory
621 fileSpec
= g_strdup( str
);
628 * Attempt to read file, testing for valid VCard format.
629 * Return: TRUE if file appears to be valid format.
631 gint
vcard_test_read_file( const gchar
*fileSpec
) {
633 gchar
*tagtemp
= NULL
, *tagname
= NULL
, *tagvalue
= NULL
, *tagtype
= NULL
, *tagrest
= NULL
, *line
;
637 if( ! fileSpec
) return MGU_NO_FILE
;
639 cardFile
= vcard_create_path( fileSpec
);
640 cardFile
->retVal
= MGU_SUCCESS
;
641 vcard_open_file( cardFile
);
642 if( cardFile
->retVal
== MGU_SUCCESS
) {
643 cardFile
->retVal
= MGU_BAD_FORMAT
;
645 lines
= VCARD_TEST_LINES
;
648 if( ( line
= vcard_get_line( cardFile
) ) == NULL
) break;
651 tagtemp
= vcard_get_tagname( line
, VCARD_SEP_TAG
);
653 tagvalue
= vcard_get_tagvalue( line
, VCARD_SEP_TAG
);
654 tagname
= vcard_get_tagname( tagtemp
, VCARD_SEP_TYPE
);
655 tagtype
= vcard_get_tagvalue( tagtemp
, VCARD_SEP_TYPE
);
656 if( tagname
== NULL
) {
662 if( g_strcasecmp( tagtype
, VCARD_TYPE_QP
) == 0 ) {
663 // Quoted-Printable: could span multiple lines
664 tagvalue
= vcard_read_qp( cardFile
, tagvalue
);
665 vcard_unescape_qp( tagvalue
);
668 if( g_strcasecmp( tagname
, VCARD_TAG_START
) == 0 && g_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
671 if( g_strcasecmp( tagname
, VCARD_TAG_END
) == 0 && g_strcasecmp( tagvalue
, VCARD_NAME
) == 0 ) {
673 if( haveStart
) cardFile
->retVal
= MGU_SUCCESS
;
681 vcard_close_file( cardFile
);
683 retVal
= cardFile
->retVal
;
684 vcard_free( cardFile
);