Fix CID 1491093: attrib leaked if attvalue is null
[claws.git] / src / addrclip.c
blob215efaaf550ddfd987a62f25e5072f2e49c3ed8f
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2022 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 * Contains address clipboard objects and related functions. The address
21 * clipboard is implemented as a linked list of AddrSelectItem objects.
22 * The address clipboard offers two groups of functions:
24 * a) Cut, copy and paste of address item objects (ItemFolder, ItemGroup,
25 * ItemPerson) into a folder. With this method, we can paste ItemPerson
26 * objects but not unattached ItemEMail objects into a folder. ItemEMail
27 * objects are owned by an ItemPerson object. Any ItemEMail objects that
28 * appear in the clipboard are ignored. If an ItemPerson object is found,
29 * the ItemPerson *and* ItemEMail objects that it owns are pasted.
31 * b) Copy and paste of ItemEMail address objects only into (below)
32 * ItemPerson objects. All ItemEMail objects which are owned by
33 * ItemPerson and referenced by ItemGroup objects are pasted. Any
34 * ItemFolder objects in the clipboard, and any objects owned by
35 * ItemFolder objects are ignored.
37 * Objects are inserted to the clipboard by copying (cloning)
38 * AddrSelectItem objects from the address books selection list to the
39 * clipboard's internal selection list. The clipboard makes use of the
40 * object id's and address cache id's to access objects contained in
41 * the address cache. If the referenced object is not found, it is
42 * ignored. This eliminates the need to delete pointers in multiple
43 * linked lists when an address object is deleted.
47 #include "config.h"
49 #include <stdio.h>
50 #include <glib.h>
51 #include <glib/gi18n.h>
53 #include "addrcache.h"
54 #include "addrbook.h"
55 #include "addrselect.h"
56 #include "addrindex.h"
57 #include "addrclip.h"
58 #include "alertpanel.h"
59 #include "defs.h"
60 #include "file-utils.h"
63 * Create a clipboard.
65 AddressClipboard *addrclip_create( void ) {
66 AddressClipboard *clipBoard;
68 clipBoard = g_new0( AddressClipboard, 1 );
69 clipBoard->cutFlag = FALSE;
70 clipBoard->objectList = NULL;
71 return clipBoard;
75 * Clear clipboard.
77 void addrclip_clear( AddressClipboard *clipBoard ) {
78 GList *node;
79 AddrSelectItem *item;
81 cm_return_if_fail( clipBoard != NULL );
82 node = clipBoard->objectList;
83 while( node ) {
84 item = node->data;
85 addrselect_item_free( item );
86 node->data = NULL;
87 node = g_list_next( node );
89 g_list_free( clipBoard->objectList );
90 clipBoard->objectList = NULL;
94 * Free up a clipboard.
96 void addrclip_free( AddressClipboard *clipBoard ) {
97 cm_return_if_fail( clipBoard != NULL );
99 addrclip_clear( clipBoard );
100 clipBoard->cutFlag = FALSE;
101 g_free(clipBoard);
105 * Setup reference to address index.
107 void addrclip_set_index(
108 AddressClipboard *clipBoard, AddressIndex *addrIndex )
110 cm_return_if_fail( clipBoard != NULL );
111 cm_return_if_fail( addrIndex != NULL );
112 clipBoard->addressIndex = addrIndex;
116 * Test whether clipboard is empty.
117 * Enter: clipBoard Clipboard.
118 * Return: TRUE if clipboard is empty.
120 gboolean addrclip_is_empty( AddressClipboard *clipBoard ) {
121 gboolean retVal = TRUE;
123 if( clipBoard ) {
124 if( clipBoard->objectList ) retVal = FALSE;
126 return retVal;
130 * Add a list of address selection objects to clipbard.
131 * Enter: clipBoard Clipboard.
132 * addrList List of address selection objects.
134 void addrclip_add( AddressClipboard *clipBoard, AddrSelectList *asl ) {
135 GList *node;
137 cm_return_if_fail( clipBoard != NULL );
138 cm_return_if_fail( asl != NULL );
139 node = asl->listSelect;
140 while( node ) {
141 AddrSelectItem *item, *itemCopy;
143 item = node->data;
144 itemCopy = addrselect_item_copy( item );
145 clipBoard->objectList =
146 g_list_append( clipBoard->objectList, itemCopy );
147 node = g_list_next( node );
151 #ifdef DEBUG_ADDRBOOK
153 * Show clipboard contents.
154 * Enter: clipBoard Clipboard.
155 * stream Output stream.
157 void addrclip_list_show( AddressClipboard *clipBoard, FILE *stream ) {
158 GList *node;
159 AddrItemObject *aio;
160 AddressCache *cache;
162 cm_return_if_fail( clipBoard != NULL );
163 node = clipBoard->objectList;
164 while( node != NULL ) {
165 AddrSelectItem *item;
167 item = node->data;
168 addrselect_item_print( item, stream );
170 cache = addrindex_get_cache( clipBoard->addressIndex, item->cacheID );
171 aio = addrcache_get_object( cache, item->uid );
172 if( aio ) {
173 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
174 addritem_print_item_person( ( ItemPerson * ) aio, stream );
176 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_EMAIL ) {
177 addritem_print_item_email( ( ItemEMail * ) aio, stream );
179 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
180 addritem_print_item_group( ( ItemGroup * ) aio, stream );
182 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
183 addritem_print_item_folder( ( ItemFolder * ) aio, stream );
186 node = g_list_next( node );
189 #endif
191 /* Pasted address pointers */
192 typedef struct _AddrClip_EMail_ AddrClip_EMail;
193 struct _AddrClip_EMail_ {
194 ItemEMail *original;
195 ItemEMail *copy;
199 * Free up specified list of addresses.
201 static void addrclip_free_copy_list( GList *copyList ) {
202 GList *node;
204 node = copyList;
205 while( node ) {
206 AddrClip_EMail *em = node->data;
207 em->original = NULL;
208 em->copy = NULL;
209 g_free( em );
210 em = NULL;
211 node = g_list_next( node );
216 * Paste person into cache.
217 * Enter: cache Address cache to paste into.
218 * folder Folder to store
219 * person Person to paste.
220 * copyLIst List of email addresses pasted.
221 * Return: Update list of email addresses pasted.
223 static GList *addrclip_cache_add_person(
224 AddressCache *cache, ItemFolder *folder, ItemPerson *person,
225 GList *copyList )
227 ItemPerson *newPerson;
228 ItemEMail *email;
229 ItemEMail *newEMail;
230 UserAttribute *attrib;
231 UserAttribute *newAttrib;
232 GList *node;
233 AddrClip_EMail *em;
235 /* Copy person */
236 newPerson = addritem_copy_item_person( person );
237 addrcache_id_person( cache, newPerson );
238 addrcache_folder_add_person( cache, folder, newPerson );
240 /* Copy email addresses */
241 node = person->listEMail;
242 while( node ) {
243 email = node->data;
244 newEMail = addritem_copy_item_email( email );
245 addrcache_id_email( cache, newEMail );
246 addrcache_person_add_email( cache, newPerson, newEMail );
247 node = g_list_next( node );
249 /* Take a copy of the original */
250 em = g_new0( AddrClip_EMail, 1 );
251 em->original = email;
252 em->copy = newEMail;
253 copyList = g_list_append( copyList, em );
256 /* Copy user attributes */
257 node = person->listAttrib;
258 while( node ) {
259 attrib = node->data;
260 newAttrib = addritem_copy_attribute( attrib );
261 addrcache_id_attribute( cache, newAttrib );
262 addritem_person_add_attribute( newPerson, newAttrib );
263 node = g_list_next( node );
266 /* Set picture name and create picture file (from copy) if missing */
267 addritem_person_set_picture(newPerson, ADDRITEM_ID(newPerson));
268 if( strcmp(ADDRITEM_ID(newPerson), ADDRITEM_ID(person)) ) {
269 gchar *pictureFile;
270 gchar *newPictureFile;
272 pictureFile = g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S, ADDRBOOK_DIR, G_DIR_SEPARATOR_S,
273 person->picture, ".png", NULL );
274 newPictureFile = g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S, ADDRBOOK_DIR, G_DIR_SEPARATOR_S,
275 newPerson->picture, ".png", NULL );
276 if (file_exist(pictureFile, FALSE) && !file_exist(newPictureFile, FALSE)) {
277 debug_print("copying contact picture file: %s -> %s\n", person->picture, newPerson->picture);
278 copy_file(pictureFile, newPictureFile, FALSE);
280 g_free( pictureFile );
281 g_free( newPictureFile );
284 return copyList;
288 * Search for new email record in copied email list.
289 * Enter: copyList List of copied email address mappings.
290 * emailOrig Original email item.
291 * Return: New email item corresponding to original item if pasted. Or NULL if
292 * not found.
294 static ItemEMail *addrclip_find_copied_email(
295 GList *copyList, ItemEMail *emailOrig )
297 ItemEMail *emailCopy;
298 GList *node;
299 AddrClip_EMail *em;
301 emailCopy = NULL;
302 node = copyList;
303 while( node ) {
304 em = node->data;
305 if( em->original == emailOrig ) {
306 emailCopy = em->copy;
307 break;
309 node = g_list_next( node );
311 return emailCopy;
315 * Paste group into cache.
316 * Enter: cache Address cache to paste into.
317 * folder Folder to store
318 * group Group to paste.
319 * copyList List of email addresses pasted.
320 * Return: Group added.
322 static ItemGroup *addrclip_cache_add_group(
323 AddressCache *cache, ItemFolder *folder, ItemGroup *group,
324 GList *copyList )
326 ItemGroup *newGroup;
327 ItemEMail *emailOrig, *emailCopy;
328 GList *node;
330 /* Copy group */
331 newGroup = addritem_copy_item_group( group );
332 addrcache_id_group( cache, newGroup );
333 addrcache_folder_add_group( cache, folder, newGroup );
335 /* Add references of copied addresses to group */
336 node = group->listEMail;
337 while( node ) {
338 emailOrig = ( ItemEMail * ) node->data;
339 emailCopy = addrclip_find_copied_email( copyList, emailOrig );
340 if( emailCopy ) {
341 addrcache_group_add_email( cache, newGroup, emailCopy );
343 node = g_list_next( node );
345 return newGroup;
349 * Copy specified folder into cache. Note this functions uses pointers to
350 * folders to copy from. There should not be any deleted items referenced
351 * by these pointers!!!
352 * Enter: cache Address cache to copy into.
353 * targetFolder Target folder.
354 * folder Folder to copy.
355 * Return: Folder added.
357 static ItemFolder *addrclip_cache_copy_folder(
358 AddressCache *cache, ItemFolder *targetFolder, ItemFolder *folder )
360 ItemFolder *newFolder;
361 ItemGroup *newGroup;
362 GList *node;
363 GList *copyList;
365 /* Copy folder */
366 newFolder = addritem_copy_item_folder( folder );
367 addrcache_id_folder( cache, newFolder );
368 addrcache_folder_add_folder( cache, targetFolder, newFolder );
370 /* Copy people to new folder */
371 copyList = NULL;
372 node = folder->listPerson;
373 while( node ) {
374 ItemPerson *item = node->data;
375 node = g_list_next( node );
376 copyList = addrclip_cache_add_person(
377 cache, newFolder, item, copyList );
380 /* Copy groups to new folder */
381 node = folder->listGroup;
382 while( node ) {
383 ItemGroup *item = node->data;
384 node = g_list_next( node );
385 newGroup = addrclip_cache_add_group(
386 cache, newFolder, item, copyList );
387 if (newGroup == NULL) {
388 g_message("error allocating memory for new group\n");
391 g_list_free( copyList );
393 /* Copy folders to new folder (recursive) */
394 node = folder->listFolder;
395 while( node ) {
396 ItemFolder *item = node->data;
397 node = g_list_next( node );
398 addrclip_cache_copy_folder( cache, newFolder, item );
401 return newFolder;
404 static gboolean addrclip_is_subfolder_of(ItemFolder *is_parent, ItemFolder *is_child)
406 ItemFolder *folder;
407 AddrItemObject *obj;
409 cm_return_val_if_fail(is_parent != NULL, FALSE);
410 cm_return_val_if_fail(is_child != NULL, FALSE);
412 if (is_parent == is_child)
413 return TRUE;
415 folder = is_child;
416 obj = folder->obj.parent;
417 while (obj) {
418 if ((void*)obj == (void*)is_parent)
419 return TRUE;
420 obj = obj->parent;
422 return FALSE;
426 * Paste item list into address book.
427 * Enter: cache Target address cache.
428 * folder Target folder where data is pasted.
429 * itemList List of items to paste.
430 * clipBoard Clipboard.
431 * Return: List of group or folder items added.
433 static GList *addrclip_cache_add_folder(
434 AddressCache *cache, ItemFolder *folder, GList *itemList,
435 AddressClipboard *clipBoard )
437 GList *folderGroup;
438 GList *node;
439 AddrSelectItem *item;
440 AddrItemObject *aio;
441 AddressCache *cacheFrom;
442 gboolean haveGroups;
443 GList *copyList;
445 folderGroup = NULL;
446 copyList = NULL;
447 haveGroups = FALSE;
448 node = itemList;
449 while( node ) {
450 item = node->data;
451 node = g_list_next( node );
453 cacheFrom = addrindex_get_cache(
454 clipBoard->addressIndex, item->cacheID );
455 if( cacheFrom == NULL ) continue;
456 if( item->uid ) {
457 aio = addrcache_get_object( cacheFrom, item->uid );
458 if( aio ) {
459 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
460 ItemPerson *person;
462 person = ( ItemPerson * ) aio;
463 copyList = addrclip_cache_add_person(
464 cache, folder, person, copyList );
467 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_EMAIL ) {
470 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
471 haveGroups = TRUE; /* Process later */
473 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
474 ItemFolder *itemFolder, *newFolder;
476 itemFolder = ( ItemFolder * ) aio;
477 if (!addrclip_is_subfolder_of(itemFolder, folder)) {
478 newFolder = addrclip_cache_copy_folder(
479 cache, folder, itemFolder );
480 folderGroup =
481 g_list_append( folderGroup, newFolder );
482 } else {
483 alertpanel_error(
484 _("Cannot copy a folder to itself or to its sub-structure.") );
489 else {
490 if( item->objectType == ITEMTYPE_DATASOURCE ) {
492 * Must be an address book - allow copy only if
493 * copying from a different cache.
495 if( cache != cacheFrom ) {
496 ItemFolder *itemFolder, *newFolder;
498 itemFolder = cacheFrom->rootFolder;
499 newFolder = addrclip_cache_copy_folder(
500 cache, folder, itemFolder );
501 addritem_folder_set_name( newFolder,
502 addrcache_get_name( cacheFrom ) );
503 folderGroup =
504 g_list_append( folderGroup, newFolder );
505 } else {
506 alertpanel_error(
507 _("Cannot copy an address book to itself.") );
513 /* Finally add any groups */
514 if( haveGroups ) {
515 node = itemList;
516 while( node ) {
517 item = node->data;
518 node = g_list_next( node );
519 cacheFrom = addrindex_get_cache(
520 clipBoard->addressIndex, item->cacheID );
521 if( cacheFrom == NULL ) continue;
522 aio = addrcache_get_object( cacheFrom, item->uid );
523 if( aio ) {
524 if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
525 ItemGroup *group, *newGroup;
527 group = ( ItemGroup * ) aio;
528 newGroup = addrclip_cache_add_group(
529 cache, folder, group, copyList );
530 folderGroup =
531 g_list_append( folderGroup, newGroup );
537 /* Free up stuff */
538 addrclip_free_copy_list( copyList );
539 g_list_free( copyList );
540 copyList = NULL;
542 return folderGroup;
546 * Move items in list into new folder
547 * Enter: cache Target address cache.
548 * targetFolder Target folder where data is pasted.
549 * itemList List of items to paste.
550 * clipBoard Clipboard.
551 * Return: List of group or folder items added.
553 static GList *addrclip_cache_move_items(
554 AddressCache *cache, ItemFolder *targetFolder, GList *itemList,
555 AddressClipboard *clipBoard )
557 GList *folderGroup;
558 GList *node;
559 AddrSelectItem *item;
560 AddrItemObject *aio;
561 AddressCache *cacheFrom;
563 folderGroup = NULL;
564 node = itemList;
565 while( node ) {
566 item = node->data;
567 node = g_list_next( node );
568 cacheFrom = addrindex_get_cache(
569 clipBoard->addressIndex, item->cacheID );
570 if( cacheFrom == NULL ) continue;
571 aio = addrcache_get_object( cacheFrom, item->uid );
572 if( aio ) {
573 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
574 ItemPerson *person;
576 person = ( ItemPerson * ) aio;
577 addrcache_folder_move_person(
578 cache, person, targetFolder );
580 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
581 ItemGroup *group;
583 group = ( ItemGroup * ) aio;
584 addrcache_folder_move_group(
585 cache, group, targetFolder );
586 folderGroup = g_list_append( folderGroup, group );
588 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
589 ItemFolder *folder = ( ItemFolder * ) aio;
591 if (!addrclip_is_subfolder_of(folder, targetFolder)) {
592 addrcache_folder_move_folder(
593 cache, folder, targetFolder );
594 folderGroup =
595 g_list_append( folderGroup, folder );
596 } else {
597 alertpanel_error(
598 _("Cannot move a folder to itself or to its sub-structure.") );
603 return folderGroup;
607 * Get address cache of first item in list. This assumes that all items in
608 * the clipboard are located in the same cache.
609 * Enter: clipBoard Clipboard.
610 * Return: List of group or folder items added.
612 static AddressCache *addrclip_list_get_cache( AddressClipboard *clipBoard ) {
613 AddressCache *cache;
614 GList *itemList;
615 AddrSelectItem *item;
617 cache = NULL;
618 itemList = clipBoard->objectList;
619 if( itemList ) {
620 item = itemList->data;
621 cache = addrindex_get_cache(
622 clipBoard->addressIndex, item->cacheID );
624 return cache;
628 * Paste (copy) clipboard into address book.
629 * Enter: clipBoard Clipboard.
630 * book Target address book.
631 * folder Target folder where data is pasted, or null for root folder.
632 * Return: List of group or folder items added.
634 GList *addrclip_paste_copy(
635 AddressClipboard *clipBoard, AddressBookFile *book,
636 ItemFolder *folder )
638 AddressCache *cache;
639 GList *itemList;
640 GList *folderGroup;
642 cm_return_val_if_fail( clipBoard != NULL, NULL );
644 cache = book->addressCache;
645 if( folder == NULL ) folder = cache->rootFolder;
647 folderGroup = NULL;
648 itemList = clipBoard->objectList;
649 folderGroup = addrclip_cache_add_folder(
650 cache, folder, itemList, clipBoard );
652 return folderGroup;
656 * Remove items that were cut from clipboard.
657 * Enter: clipBoard Clipboard.
659 void addrclip_delete_item( AddressClipboard *clipBoard ) {
660 AddrSelectItem *item;
661 AddrItemObject *aio;
662 AddressCache *cacheFrom;
663 GList *node;
665 /* If cutting within current cache, no deletion is necessary */
666 if( clipBoard->moveFlag ) return;
668 /* Remove groups */
669 node = clipBoard->objectList;
670 while( node ) {
671 item = node->data;
672 node = g_list_next( node );
673 cacheFrom = addrindex_get_cache(
674 clipBoard->addressIndex, item->cacheID );
675 if( cacheFrom == NULL ) continue;
676 aio = addrcache_get_object( cacheFrom, item->uid );
677 if( aio ) {
678 if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
679 ItemGroup *group;
681 group = ( ItemGroup * ) aio;
682 group = addrcache_remove_group( cacheFrom, group );
683 if( group ) {
684 addritem_free_item_group( group );
690 /* Remove persons and folders */
691 node = clipBoard->objectList;
692 while( node ) {
693 item = node->data;
694 node = g_list_next( node );
696 cacheFrom = addrindex_get_cache(
697 clipBoard->addressIndex, item->cacheID );
698 if( cacheFrom == NULL ) continue;
700 aio = addrcache_get_object( cacheFrom, item->uid );
701 if( aio ) {
702 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
703 ItemPerson *person;
705 person = ( ItemPerson * ) aio;
706 person = addrcache_remove_person( cacheFrom, person );
707 if( person ) {
708 addritem_free_item_person( person );
711 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
712 ItemFolder *itemFolder;
714 itemFolder = ( ItemFolder * ) aio;
715 itemFolder = addrcache_remove_folder_delete(
716 cacheFrom, itemFolder );
717 addritem_free_item_folder( itemFolder );
724 * Paste (move) clipboard into address book.
725 * Enter: clipBoard Clipboard.
726 * book Target address book.
727 * folder Target folder where data is pasted, or null for root folder.
728 * Return: List of group or folder items added.
730 GList *addrclip_paste_cut(
731 AddressClipboard *clipBoard, AddressBookFile *book,
732 ItemFolder *folder )
734 AddressCache *cache, *cacheFrom;
735 GList *itemList;
736 GList *folderGroup;
738 cm_return_val_if_fail( clipBoard != NULL, NULL );
740 cache = book->addressCache;
741 if( folder == NULL ) folder = cache->rootFolder;
743 folderGroup = NULL;
744 clipBoard->moveFlag = FALSE;
745 cacheFrom = addrclip_list_get_cache( clipBoard );
746 if( cacheFrom && cacheFrom == cache ) {
747 /* Move items between folders in same book */
748 itemList = clipBoard->objectList;
749 folderGroup = addrclip_cache_move_items(
750 cache, folder, itemList, clipBoard );
751 clipBoard->moveFlag = TRUE;
753 else {
754 /* Move items across address books */
755 itemList = clipBoard->objectList;
756 folderGroup = addrclip_cache_add_folder(
757 cache, folder, itemList, clipBoard );
760 return folderGroup;
763 * End of Source.