Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / storage / exchange-hierarchy-foreign.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2002-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /* ExchangeHierarchyForeign: class for a hierarchy consisting of a
21  * selected subset of folders from another user's mailbox.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <glib.h>
33 #include <glib/gstdio.h>
34
35 #include "libedataserver/e-source-list.h"
36 #include "libedataserver/e-xml-hash-utils.h"
37 #include "libedataserver/e-xml-utils.h"
38
39 #include "e-folder-exchange.h"
40 #include "e2k-propnames.h"
41 #include "e2k-types.h"
42 #include "e2k-uri.h"
43 #include "e2k-utils.h"
44 #include "exchange-account.h"
45 #include "exchange-esource.h"
46 #include "exchange-hierarchy-foreign.h"
47 #include "exchange-types.h"
48
49 struct _ExchangeHierarchyForeignPrivate {
50         GMutex *hide_private_lock;
51         gboolean checked_hide_private;
52 };
53
54 extern const char *exchange_localfreebusy_path;
55
56 #define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY_SOMEDAV
57 static ExchangeHierarchySomeDAVClass *parent_class = NULL;
58
59 static GPtrArray *get_hrefs (ExchangeHierarchySomeDAV *hsd);
60 static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
61                                                   EFolder *parent,
62                                                   const char *name,
63                                                   const char *type);
64 static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
65                                                   EFolder *folder);
66 static ExchangeAccountFolderResult scan_subtree (ExchangeHierarchy *hier,
67                                                  EFolder *folder,
68                                                  int mode);
69 static void finalize (GObject *object);
70
71 static void
72 class_init (GObjectClass *object_class)
73 {
74         ExchangeHierarchyClass *hierarchy_class =
75                 EXCHANGE_HIERARCHY_CLASS (object_class);
76         ExchangeHierarchySomeDAVClass *somedav_class =
77                 EXCHANGE_HIERARCHY_SOMEDAV_CLASS (object_class);
78
79         parent_class = g_type_class_ref (PARENT_TYPE);
80
81         /* virtual method override */
82         object_class->finalize = finalize;
83
84         hierarchy_class->create_folder  = create_folder;
85         hierarchy_class->remove_folder  = remove_folder;
86         hierarchy_class->scan_subtree   = scan_subtree;
87
88         somedav_class->get_hrefs        = get_hrefs;
89 }
90
91 static void
92 init (GObject *object)
93 {
94         ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (object);
95         ExchangeHierarchy *hier = EXCHANGE_HIERARCHY (object);
96
97         hfor->priv = g_new0 (ExchangeHierarchyForeignPrivate, 1);
98         hfor->priv->hide_private_lock = g_mutex_new ();
99         hier->hide_private_items = TRUE;
100 }
101
102 static void
103 finalize (GObject *object)
104 {
105         ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (object);
106
107         g_mutex_free (hfor->priv->hide_private_lock);
108         g_free (hfor->priv);
109
110         G_OBJECT_CLASS (parent_class)->finalize (object);
111 }
112
113 E2K_MAKE_TYPE (exchange_hierarchy_foreign, ExchangeHierarchyForeign, class_init, init, PARENT_TYPE)
114
115 static const char *privacy_props[] = {
116         PR_DELEGATES_ENTRYIDS,
117         PR_DELEGATES_SEE_PRIVATE,
118 };
119 static const int n_privacy_props = sizeof (privacy_props) / sizeof (privacy_props[0]);
120
121 static void
122 check_hide_private (ExchangeHierarchy *hier)
123 {
124         ExchangeHierarchyForeign *hfor = EXCHANGE_HIERARCHY_FOREIGN (hier);
125         E2kContext *ctx;
126         E2kHTTPStatus status;
127         E2kResult *results;
128         int nresults = 0, i;
129         GPtrArray *entryids, *privflags;
130         GByteArray *entryid;
131         const char *my_dn, *delegate_dn;
132         char *uri;
133
134         g_mutex_lock (hfor->priv->hide_private_lock);
135
136         if (hfor->priv->checked_hide_private) {
137                 g_mutex_unlock (hfor->priv->hide_private_lock);
138                 return;
139         }
140
141         uri = e2k_uri_concat (hier->account->home_uri,
142                               "NON_IPM_SUBTREE/Freebusy%20Data/LocalFreebusy.EML");
143         ctx = exchange_account_get_context (hier->account);
144
145         status = e2k_context_propfind (ctx, NULL, uri,
146                                        privacy_props, n_privacy_props,
147                                        &results, &nresults);
148         g_free (uri);
149
150         hfor->priv->checked_hide_private = TRUE;
151
152         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0) {
153                 g_mutex_unlock (hfor->priv->hide_private_lock);
154                 return;
155         }
156         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (results[0].status) ||
157             !results[0].props || nresults > 1) {
158                 e2k_results_free (results, nresults);
159                 g_mutex_unlock (hfor->priv->hide_private_lock);
160                 return;
161         }
162
163         entryids  = e2k_properties_get_prop (results[0].props,
164                                              PR_DELEGATES_ENTRYIDS);
165         privflags = e2k_properties_get_prop (results[0].props,
166                                              PR_DELEGATES_SEE_PRIVATE);
167         if (entryids && privflags) {
168                 my_dn = hier->account->legacy_exchange_dn;
169                 for (i = 0; i < entryids->len && i < privflags->len; i++) {
170                         entryid = entryids->pdata[i];
171                         delegate_dn = e2k_entryid_to_dn (entryid);
172
173                         if (delegate_dn &&
174                             !g_ascii_strcasecmp (delegate_dn, my_dn) &&
175                             privflags->pdata[i] &&
176                             atoi (privflags->pdata[i]))
177                                 hier->hide_private_items = FALSE;
178                         break;
179                 }
180         }
181
182         e2k_results_free (results, nresults);
183         g_mutex_unlock (hfor->priv->hide_private_lock);
184 }
185
186 static void
187 remove_all_cb (ExchangeHierarchy *hier, EFolder *folder, gpointer user_data)
188 {
189         exchange_hierarchy_removed_folder (hier, folder);
190 }
191
192 static void
193 hierarchy_foreign_cleanup (ExchangeHierarchy *hier)
194 {
195         char *mf_path;
196
197         exchange_hierarchy_webdav_offline_scan_subtree (hier, remove_all_cb,
198                                                         NULL);
199
200         mf_path = e_folder_exchange_get_storage_file (hier->toplevel, "hierarchy.xml");
201         g_unlink (mf_path);
202         g_free (mf_path);
203
204         exchange_hierarchy_removed_folder (hier, hier->toplevel);
205 }
206
207 static const char *folder_props[] = {
208         E2K_PR_EXCHANGE_FOLDER_CLASS,
209         E2K_PR_HTTPMAIL_UNREAD_COUNT,
210         E2K_PR_DAV_DISPLAY_NAME,
211         PR_ACCESS
212 };
213 static const int n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
214
215 static ExchangeAccountFolderResult
216 find_folder (ExchangeHierarchy *hier, const char *uri, EFolder **folder_out)
217 {
218         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
219         E2kContext *ctx = exchange_account_get_context (hier->account);
220         E2kHTTPStatus status;
221         E2kResult *results;
222         int nresults = 0;
223         EFolder *folder;
224         const char *access;
225
226         status = e2k_context_propfind (ctx, NULL, uri,
227                                        folder_props, n_folder_props,
228                                        &results, &nresults);
229         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
230                 return exchange_hierarchy_webdav_status_to_folder_result (status);
231
232         if (nresults == 0)
233                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
234
235         access = e2k_properties_get_prop (results[0].props, PR_ACCESS);
236         if (!access || !atoi (access)) {
237                 e2k_results_free (results, nresults);
238                 return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
239         }
240
241         folder = exchange_hierarchy_webdav_parse_folder (hwd, hier->toplevel,
242                                                          &results[0]);
243         e2k_results_free (results, nresults);
244
245         if (!folder)
246                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
247
248         exchange_hierarchy_new_folder (hier, folder);
249
250         if (folder_out)
251                 *folder_out = folder;
252         else
253                 g_object_unref (folder);
254         return EXCHANGE_ACCOUNT_FOLDER_OK;
255 }
256
257 static struct {
258         const char *name, *prop;
259 } std_folders[] = {
260         { N_("Calendar"),       E2K_PR_STD_FOLDER_CALENDAR },
261         { N_("Contacts"),       E2K_PR_STD_FOLDER_CONTACTS },
262         { N_("Deleted Items"),  E2K_PR_STD_FOLDER_DELETED_ITEMS },
263         { N_("Drafts"),         E2K_PR_STD_FOLDER_DRAFTS },
264         { N_("Inbox"),          E2K_PR_STD_FOLDER_INBOX },
265         { N_("Journal"),        E2K_PR_STD_FOLDER_JOURNAL },
266         { N_("Notes"),          E2K_PR_STD_FOLDER_NOTES },
267         { N_("Outbox"),         E2K_PR_STD_FOLDER_OUTBOX },
268         { N_("Sent Items"),     E2K_PR_STD_FOLDER_SENT_ITEMS },
269         { N_("Tasks"),          E2K_PR_STD_FOLDER_TASKS }
270 };
271 const static int n_std_folders = sizeof (std_folders) / sizeof (std_folders[0]);
272 static ExchangeAccountFolderResult
273 create_internal (ExchangeHierarchy *hier, EFolder *parent,
274                  const char *name, const char *type, EFolder **folder_out)
275 {
276         ExchangeAccountFolderResult result;
277         char *literal_uri = NULL, *standard_uri = NULL;
278         const char *home_uri;
279         int i;
280
281         /* For now, no nesting */
282         if (parent != hier->toplevel || strchr (name + 1, '/'))
283                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
284
285         check_hide_private (hier);
286
287         home_uri = e_folder_exchange_get_internal_uri (hier->toplevel);
288         literal_uri = e2k_uri_concat (home_uri, name);
289         if (exchange_account_get_folder (hier->account, literal_uri)) {
290                 g_free (literal_uri);
291                 if (exchange_hierarchy_is_empty (hier))
292                         hierarchy_foreign_cleanup (hier);
293                 return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
294         }
295
296         for (i = 0; i < n_std_folders; i++) {
297                 if (g_ascii_strcasecmp (std_folders[i].name, name) != 0 &&
298                     g_utf8_collate (_(std_folders[i].name), name) != 0)
299                         continue;
300
301                 standard_uri = exchange_account_get_standard_uri_for (
302                         hier->account, home_uri, std_folders[i].prop);
303                 if (!standard_uri)
304                         break;
305                 if (!strcmp (literal_uri, standard_uri)) {
306                         g_free (standard_uri);
307                         standard_uri = NULL;
308                         break;
309                 }
310
311                 if (exchange_account_get_folder (hier->account, standard_uri)) {
312                         g_free (standard_uri);
313                         g_free (literal_uri);
314                         if (exchange_hierarchy_is_empty (hier))
315                                 hierarchy_foreign_cleanup (hier);
316                         return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
317                 }
318
319                 break;
320         }
321
322         result = find_folder (hier, literal_uri, folder_out);
323         if (result == EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST && standard_uri)
324                 result = find_folder (hier, standard_uri, folder_out);
325
326         g_free (literal_uri);
327         g_free (standard_uri);
328
329         /* If the hierarchy is now empty, then we must have just been
330          * created but then the add failed. So remove it again.
331          */
332         if (exchange_hierarchy_is_empty (hier))
333                 hierarchy_foreign_cleanup (hier);
334
335         return result;
336 }
337
338
339 static ExchangeAccountFolderResult
340 create_folder (ExchangeHierarchy *hier, EFolder *parent,
341                const char *name, const char *type)
342 {
343         return create_internal (hier, parent, name, type, NULL);
344 }
345
346 static ExchangeAccountFolderResult
347 remove_folder (ExchangeHierarchy *hier, EFolder *folder)
348 {
349         const char *folder_type, *physical_uri;
350
351         /* Temp Fix for remove fav folders. see #59168 */
352         /* remove ESources */
353         folder_type = e_folder_get_type_string (folder);
354         physical_uri = e_folder_get_physical_uri (folder);
355
356         if (strcmp (folder_type, "calendar") == 0) {
357                 remove_folder_esource (hier->account,
358                                        EXCHANGE_CALENDAR_FOLDER,
359                                        physical_uri);
360         }
361         else if (strcmp (folder_type, "tasks") == 0) {
362                 remove_folder_esource (hier->account,
363                                        EXCHANGE_TASKS_FOLDER,
364                                        physical_uri);
365         }
366         else if (strcmp (folder_type, "contacts") == 0) {
367                 remove_folder_esource (hier->account,
368                                        EXCHANGE_CONTACTS_FOLDER,
369                                        physical_uri);
370         }
371         if (folder != hier->toplevel)
372                 exchange_hierarchy_removed_folder (hier, folder);
373
374         if (folder == hier->toplevel || exchange_hierarchy_is_empty (hier))
375                 hierarchy_foreign_cleanup (hier);
376
377         return EXCHANGE_ACCOUNT_FOLDER_OK;
378 }
379
380 static ExchangeAccountFolderResult
381 scan_subtree (ExchangeHierarchy *hier, EFolder *folder, int mode)
382 {
383         ExchangeAccountFolderResult folder_result;
384
385         check_hide_private (hier);
386
387         folder_result = EXCHANGE_HIERARCHY_CLASS (parent_class)->scan_subtree (hier, folder, mode);
388
389         if (exchange_hierarchy_is_empty (hier))
390                 hierarchy_foreign_cleanup (hier);
391
392         return folder_result;
393 }
394
395 static void
396 add_href (ExchangeHierarchy *hier, EFolder *folder, gpointer hrefs)
397 {
398         char *uri = g_strdup (e_folder_exchange_get_internal_uri (folder));
399
400         g_ptr_array_add (hrefs, (gpointer) uri);
401 }
402
403 static GPtrArray *
404 get_hrefs (ExchangeHierarchySomeDAV *hsd)
405 {
406         GPtrArray *hrefs;
407
408         hrefs = g_ptr_array_new ();
409         exchange_hierarchy_webdav_offline_scan_subtree (EXCHANGE_HIERARCHY (hsd), add_href, hrefs);
410         return hrefs;
411 }
412
413 /**
414  * exchange_hierarchy_foreign_add_folder:
415  * @hier: the hierarchy
416  * @folder_name: the name of the folder to add
417  * @folder: on successful return, the created folder
418  *
419  * Adds a new folder to @hier.
420  *
421  * Return value: the folder result.
422  **/
423 ExchangeAccountFolderResult
424 exchange_hierarchy_foreign_add_folder (ExchangeHierarchy *hier,
425                                        const char *folder_name,
426                                        EFolder **folder)
427 {
428         ExchangeAccountFolderResult result;
429         const char *folder_type = NULL;
430         const char *physical_uri = NULL;
431         char *new_folder_name;
432         guint folder_mask = 0;
433
434         result =  create_internal (hier, hier->toplevel, folder_name, NULL, folder);
435         if (result == EXCHANGE_ACCOUNT_FOLDER_OK) {
436                 /* Add the esources */
437                 folder_type = e_folder_get_type_string (*folder);
438                 physical_uri = e_folder_get_physical_uri (*folder);
439                 new_folder_name = g_strdup_printf("%s's %s", 
440                                         hier->owner_name, folder_name);
441
442                 if (!(strcmp (folder_type, "calendar")) ||
443                 !(strcmp (folder_type, "calendar/public"))) {
444                         folder_mask = EXCHANGE_CALENDAR_FOLDER | FORIEGN_FOLDER_FLAG;
445                         add_folder_esource (hier->account,
446                                             folder_mask,
447                                             new_folder_name,
448                                             physical_uri);
449                 }
450                 else if (!(strcmp (folder_type, "tasks")) ||
451                          !(strcmp (folder_type, "tasks/public"))) {
452                         folder_mask = EXCHANGE_TASKS_FOLDER | FORIEGN_FOLDER_FLAG;
453                                 add_folder_esource (hier->account,
454                                                     folder_mask,
455                                                     new_folder_name,
456                                                     physical_uri);
457                 }
458                 else if (!(strcmp (folder_type, "contacts")) ||
459                          !(strcmp (folder_type, "contacts/public")) ||
460                          !(strcmp (folder_type, "contacts/ldap"))) {
461                                 folder_mask = EXCHANGE_CONTACTS_FOLDER | FORIEGN_FOLDER_FLAG;
462                                 add_folder_esource (hier->account,
463                                                     folder_mask,
464                                                     new_folder_name,
465                                                     physical_uri);
466                 }
467                 g_free (new_folder_name);
468         }
469         return result;
470 }
471
472 static ExchangeHierarchy *
473 hierarchy_foreign_new (ExchangeAccount *account,
474                        const char *hierarchy_name,
475                        const char *physical_uri_prefix,
476                        const char *internal_uri_prefix,
477                        const char *owner_name,
478                        const char *owner_email,
479                        const char *source_uri)
480 {
481         ExchangeHierarchyForeign *hfor;
482
483         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
484
485         hfor = g_object_new (EXCHANGE_TYPE_HIERARCHY_FOREIGN, NULL);
486
487         exchange_hierarchy_webdav_construct (EXCHANGE_HIERARCHY_WEBDAV (hfor),
488                                              account,
489                                              EXCHANGE_HIERARCHY_FOREIGN,
490                                              hierarchy_name,
491                                              physical_uri_prefix,
492                                              internal_uri_prefix,
493                                              owner_name, owner_email,
494                                              source_uri,
495                                              FALSE);
496
497         return EXCHANGE_HIERARCHY (hfor);
498 }
499
500 /**
501  * exchange_hierarchy_foreign_new:
502  * @account: an #ExchangeAccount
503  * @hierarchy_name: the name of the hierarchy
504  * @physical_uri_prefix: the prefix of physical URIs in this hierarchy
505  * @internal_uri_prefix: the prefix of internal (http) URIs in this hierarchy
506  * @owner_name: display name of the owner of the hierarchy
507  * @owner_email: email address of the owner of the hierarchy
508  * @source_uri: evolution-mail source uri for the hierarchy
509  *
510  * Creates a new (initially empty) hierarchy for another user's
511  * folders.
512  *
513  * Return value: the new hierarchy.
514  **/
515 ExchangeHierarchy *
516 exchange_hierarchy_foreign_new (ExchangeAccount *account,
517                                 const char *hierarchy_name,
518                                 const char *physical_uri_prefix,
519                                 const char *internal_uri_prefix,
520                                 const char *owner_name,
521                                 const char *owner_email,
522                                 const char *source_uri)
523 {
524         ExchangeHierarchy *hier;
525         char *mf_path;
526         GHashTable *props;
527         xmlDoc *doc;
528
529         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
530         
531         hier = hierarchy_foreign_new (account, hierarchy_name,
532                                       physical_uri_prefix,
533                                       internal_uri_prefix,
534                                       owner_name, owner_email,
535                                       source_uri);
536
537         props = g_hash_table_new (g_str_hash, g_str_equal);
538         g_hash_table_insert (props, "name", (char *)hierarchy_name);
539         g_hash_table_insert (props, "physical_uri_prefix",
540                              (char *)physical_uri_prefix);
541         g_hash_table_insert (props, "internal_uri_prefix",
542                              (char *)internal_uri_prefix);
543         g_hash_table_insert (props, "owner_name", (char *)owner_name);
544         g_hash_table_insert (props, "owner_email", (char *)owner_email);
545         g_hash_table_insert (props, "source_uri", (char *)source_uri);
546
547         mf_path = e_folder_exchange_get_storage_file (hier->toplevel, "hierarchy.xml");
548         doc = e_xml_from_hash (props, E_XML_HASH_TYPE_PROPERTY,
549                                "foreign-hierarchy");
550         e_xml_save_file (mf_path, doc);
551         g_hash_table_destroy (props);
552         g_free (mf_path);
553         xmlFreeDoc (doc);
554
555         return hier;
556 }
557
558 /**
559  * exchange_hierarchy_foreign_new_from_dir:
560  * @account: an #ExchangeAccount
561  * @folder_path: pathname to a directory containing a hierarchy.xml file
562  *
563  * Recreates a new hierarchy from saved values.
564  *
565  * Return value: the new hierarchy.
566  **/
567 ExchangeHierarchy *
568 exchange_hierarchy_foreign_new_from_dir (ExchangeAccount *account,
569                                          const char *folder_path)
570 {
571         ExchangeHierarchy *hier;
572         char *mf_path;
573         GHashTable *props;
574         xmlDoc *doc;
575
576         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
577         g_return_val_if_fail (folder_path != NULL, NULL);
578
579         mf_path = g_build_filename (folder_path, "hierarchy.xml", NULL);
580
581         doc = e_xml_parse_file (mf_path);
582         g_free (mf_path);
583
584         if (!doc)
585                 return NULL;
586
587         props = e_xml_to_hash (doc, E_XML_HASH_TYPE_PROPERTY);
588         xmlFreeDoc (doc);
589
590         hier = hierarchy_foreign_new (account,
591                                       g_hash_table_lookup (props, "name"),
592                                       g_hash_table_lookup (props, "physical_uri_prefix"),
593                                       g_hash_table_lookup (props, "internal_uri_prefix"),
594                                       g_hash_table_lookup (props, "owner_name"),
595                                       g_hash_table_lookup (props, "owner_email"),
596                                       g_hash_table_lookup (props, "source_uri"));
597
598         e_xml_destroy_hash (props);
599         return hier;
600 }