Merge branch 'camel-socks-proxy-master'
[platform/upstream/evolution-data-server.git] / camel / camel-service.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-service.c : Abstract class for an email service */
3
4 /*
5  *
6  * Author :
7  *  Bertrand Guiheneuf <bertrand@helixcode.com>
8  *
9  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU Lesser General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib/gi18n-lib.h>
36
37 #include "camel-debug.h"
38 #include "camel-operation.h"
39 #include "camel-service.h"
40 #include "camel-session.h"
41
42 #define d(x)
43 #define w(x)
44
45 #define CAMEL_SERVICE_GET_PRIVATE(obj) \
46         (G_TYPE_INSTANCE_GET_PRIVATE \
47         ((obj), CAMEL_TYPE_SERVICE, CamelServicePrivate))
48
49 struct _CamelServicePrivate {
50         GStaticRecMutex connect_lock;   /* for locking connection operations */
51         GStaticMutex connect_op_lock;   /* for locking the connection_op */
52 };
53
54 G_DEFINE_ABSTRACT_TYPE (CamelService, camel_service, CAMEL_TYPE_OBJECT)
55
56 static void
57 service_finalize (GObject *object)
58 {
59         CamelService *service = CAMEL_SERVICE (object);
60
61         if (service->status == CAMEL_SERVICE_CONNECTED)
62                 CAMEL_SERVICE_GET_CLASS (service)->disconnect (service, TRUE, NULL);
63
64         if (service->url)
65                 camel_url_free (service->url);
66
67         if (service->session)
68                 g_object_unref (service->session);
69
70         g_static_rec_mutex_free (&service->priv->connect_lock);
71         g_static_mutex_free (&service->priv->connect_op_lock);
72
73         /* Chain up to parent's finalize() method. */
74         G_OBJECT_CLASS (camel_service_parent_class)->finalize (object);
75 }
76
77 static gboolean
78 service_construct (CamelService *service,
79                    CamelSession *session,
80                    CamelProvider *provider,
81                    CamelURL *url,
82                    GError **error)
83 {
84         gchar *err, *url_string;
85
86         if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) &&
87             (url->user == NULL || url->user[0] == '\0')) {
88                 err = _("URL '%s' needs a username component");
89                 goto fail;
90         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) &&
91                    (url->host == NULL || url->host[0] == '\0')) {
92                 err = _("URL '%s' needs a host component");
93                 goto fail;
94         } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) &&
95                    (url->path == NULL || url->path[0] == '\0')) {
96                 err = _("URL '%s' needs a path component");
97                 goto fail;
98         }
99
100         service->provider = provider;
101         service->url = camel_url_copy (url);
102         service->session = g_object_ref (session);
103
104         service->status = CAMEL_SERVICE_DISCONNECTED;
105
106         return TRUE;
107
108 fail:
109         url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD);
110         g_set_error (
111                 error, CAMEL_SERVICE_ERROR,
112                 CAMEL_SERVICE_ERROR_URL_INVALID,
113                 err, url_string);
114         g_free (url_string);
115
116         return FALSE;
117 }
118
119 static gboolean
120 service_connect (CamelService *service,
121                  GError **error)
122 {
123         /* Things like the CamelMboxStore can validly
124          * not define a connect function. */
125          return TRUE;
126 }
127
128 static gboolean
129 service_disconnect (CamelService *service,
130                     gboolean clean,
131                     GError **error)
132 {
133         /* We let people get away with not having a disconnect
134          * function -- CamelMboxStore, for example. */
135         return TRUE;
136 }
137
138 static void
139 service_cancel_connect (CamelService *service)
140 {
141         camel_operation_cancel (service->connect_op);
142 }
143
144 static GList *
145 service_query_auth_types (CamelService *service,
146                           GError **error)
147 {
148         return NULL;
149 }
150
151 static gchar *
152 service_get_name (CamelService *service,
153                   gboolean brief)
154 {
155         g_warning (
156                 "%s does not implement CamelServiceClass::get_name()",
157                 G_OBJECT_TYPE_NAME (service));
158
159         return g_strdup (G_OBJECT_TYPE_NAME (service));
160 }
161
162 static gchar *
163 service_get_path (CamelService *service)
164 {
165         CamelProvider *prov = service->provider;
166         CamelURL *url = service->url;
167         GString *gpath;
168         gchar *path;
169
170         /* A sort of ad-hoc default implementation that works for our
171          * current set of services.
172          */
173
174         gpath = g_string_new (service->provider->protocol);
175         if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) {
176                 if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
177                         g_string_append_printf (gpath, "/%s@%s",
178                                                 url->user ? url->user : "",
179                                                 url->host ? url->host : "");
180
181                         if (url->port)
182                                 g_string_append_printf (gpath, ":%d", url->port);
183                 } else {
184                         g_string_append_printf (gpath, "/%s%s", url->user ? url->user : "",
185                                                 CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
186                 }
187         } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
188                 g_string_append_printf (gpath, "/%s%s",
189                                         CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@",
190                                         url->host ? url->host : "");
191
192                 if (url->port)
193                         g_string_append_printf (gpath, ":%d", url->port);
194         }
195
196         if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH))
197                 g_string_append_printf (gpath, "%s%s", *url->path == '/' ? "" : "/", url->path);
198
199         path = gpath->str;
200         g_string_free (gpath, FALSE);
201
202         return path;
203 }
204
205 static void
206 camel_service_class_init (CamelServiceClass *class)
207 {
208         GObjectClass *object_class;
209
210         g_type_class_add_private (class, sizeof (CamelServicePrivate));
211
212         object_class = G_OBJECT_CLASS (class);
213         object_class->finalize = service_finalize;
214
215         class->construct = service_construct;
216         class->connect = service_connect;
217         class->disconnect = service_disconnect;
218         class->cancel_connect = service_cancel_connect;
219         class->query_auth_types = service_query_auth_types;
220         class->get_name = service_get_name;
221         class->get_path = service_get_path;
222 }
223
224 static void
225 camel_service_init (CamelService *service)
226 {
227         service->priv = CAMEL_SERVICE_GET_PRIVATE (service);
228
229         g_static_rec_mutex_init (&service->priv->connect_lock);
230         g_static_mutex_init (&service->priv->connect_op_lock);
231 }
232
233 GQuark
234 camel_service_error_quark (void)
235 {
236         static GQuark quark = 0;
237
238         if (G_UNLIKELY (quark == 0)) {
239                 const gchar *string = "camel-service-error-quark";
240                 quark = g_quark_from_static_string (string);
241         }
242
243         return quark;
244 }
245
246 /**
247  * camel_service_construct:
248  * @service: a #CamelService object
249  * @session: the #CamelSession for @service
250  * @provider: the #CamelProvider associated with @service
251  * @url: the default URL for the service (may be %NULL)
252  * @error: return location for a #GError, or %NULL
253  *
254  * Constructs a #CamelService initialized with the given parameters.
255  *
256  * Returns: %TRUE on success, %FALSE on failure
257  **/
258 gboolean
259 camel_service_construct (CamelService *service,
260                          CamelSession *session,
261                          CamelProvider *provider,
262                          CamelURL *url,
263                          GError **error)
264 {
265         CamelServiceClass *class;
266         gboolean success;
267
268         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
269         g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
270
271         class = CAMEL_SERVICE_GET_CLASS (service);
272         g_return_val_if_fail (class->construct != NULL, FALSE);
273
274         success = class->construct (service, session, provider, url, error);
275         CAMEL_CHECK_GERROR (service, construct, success, error);
276
277         return success;
278 }
279
280 /**
281  * camel_service_connect:
282  * @service: a #CamelService object
283  * @error: return location for a #GError, or %NULL
284  *
285  * Connect to the service using the parameters it was initialized
286  * with.
287  *
288  * Returns: %TRUE if the connection is made or %FALSE otherwise
289  **/
290 gboolean
291 camel_service_connect (CamelService *service,
292                        GError **error)
293 {
294         CamelServiceClass *class;
295         gboolean ret = FALSE;
296         gboolean unreg = FALSE;
297         CamelOperation *connect_op;
298
299         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
300         g_return_val_if_fail (service->session != NULL, FALSE);
301         g_return_val_if_fail (service->url != NULL, FALSE);
302
303         class = CAMEL_SERVICE_GET_CLASS (service);
304         g_return_val_if_fail (class->connect != NULL, FALSE);
305
306         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
307
308         if (service->status == CAMEL_SERVICE_CONNECTED) {
309                 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
310                 return TRUE;
311         }
312
313         /* Register a separate operation for connecting, so that
314          * the offline code can cancel it. */
315         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
316         service->connect_op = camel_operation_registered ();
317         if (!service->connect_op) {
318                 service->connect_op = camel_operation_new ();
319                 camel_operation_register (service->connect_op);
320                 unreg = TRUE;
321         }
322         connect_op = service->connect_op;
323         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
324
325         service->status = CAMEL_SERVICE_CONNECTING;
326         ret = class->connect (service, error);
327         CAMEL_CHECK_GERROR (service, connect, ret, error);
328         service->status = ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
329
330         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
331         if (connect_op) {
332                 if (unreg && service->connect_op)
333                         camel_operation_unregister ();
334
335                 g_object_unref (connect_op);
336                 service->connect_op = NULL;
337         }
338         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
339
340         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
341
342         return ret;
343 }
344
345 /**
346  * camel_service_disconnect:
347  * @service: a #CamelService object
348  * @clean: whether or not to try to disconnect cleanly
349  * @error: return location for a #GError, or %NULL
350  *
351  * Disconnect from the service. If @clean is %FALSE, it should not
352  * try to do any synchronizing or other cleanup of the connection.
353  *
354  * Returns: %TRUE if the disconnect was successful or %FALSE otherwise
355  **/
356 gboolean
357 camel_service_disconnect (CamelService *service,
358                           gboolean clean,
359                           GError **error)
360 {
361         CamelServiceClass *class;
362         gboolean res = TRUE;
363         gint unreg = FALSE;
364
365         g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
366
367         class = CAMEL_SERVICE_GET_CLASS (service);
368         g_return_val_if_fail (class->disconnect != NULL, FALSE);
369
370         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
371
372         if (service->status != CAMEL_SERVICE_DISCONNECTED
373             && service->status != CAMEL_SERVICE_DISCONNECTING) {
374                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
375                 service->connect_op = camel_operation_registered ();
376                 if (!service->connect_op) {
377                         service->connect_op = camel_operation_new ();
378                         camel_operation_register (service->connect_op);
379                         unreg = TRUE;
380                 }
381                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
382
383                 service->status = CAMEL_SERVICE_DISCONNECTING;
384                 res = class->disconnect (service, clean, error);
385                 CAMEL_CHECK_GERROR (service, disconnect, res, error);
386                 service->status = CAMEL_SERVICE_DISCONNECTED;
387
388                 camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
389                 if (unreg)
390                         camel_operation_unregister ();
391
392                 g_object_unref (service->connect_op);
393                 service->connect_op = NULL;
394                 camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
395         }
396
397         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
398
399         service->status = CAMEL_SERVICE_DISCONNECTED;
400
401         return res;
402 }
403
404 /**
405  * camel_service_cancel_connect:
406  * @service: a #CamelService object
407  *
408  * If @service is currently attempting to connect to or disconnect
409  * from a server, this causes it to stop and fail. Otherwise it is a
410  * no-op.
411  **/
412 void
413 camel_service_cancel_connect (CamelService *service)
414 {
415         CamelServiceClass *class;
416
417         g_return_if_fail (CAMEL_IS_SERVICE (service));
418
419         class = CAMEL_SERVICE_GET_CLASS (service);
420         g_return_if_fail (class->cancel_connect != NULL);
421
422         camel_service_lock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
423         if (service->connect_op)
424                 class->cancel_connect (service);
425         camel_service_unlock (service, CAMEL_SERVICE_CONNECT_OP_LOCK);
426 }
427
428 /**
429  * camel_service_get_url:
430  * @service: a #CamelService object
431  *
432  * Gets the URL representing @service. The returned URL must be
433  * freed when it is no longer needed. For security reasons, this
434  * routine does not return the password.
435  *
436  * Returns: the URL representing @service
437  **/
438 gchar *
439 camel_service_get_url (CamelService *service)
440 {
441         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
442
443         return camel_url_to_string (service->url, CAMEL_URL_HIDE_PASSWORD);
444 }
445
446 /**
447  * camel_service_get_name:
448  * @service: a #CamelService object
449  * @brief: whether or not to use a briefer form
450  *
451  * This gets the name of the service in a "friendly" (suitable for
452  * humans) form. If @brief is %TRUE, this should be a brief description
453  * such as for use in the folder tree. If @brief is %FALSE, it should
454  * be a more complete and mostly unambiguous description.
455  *
456  * Returns: a description of the service which the caller must free
457  **/
458 gchar *
459 camel_service_get_name (CamelService *service,
460                         gboolean brief)
461 {
462         CamelServiceClass *class;
463
464         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
465         g_return_val_if_fail (service->url, NULL);
466
467         class = CAMEL_SERVICE_GET_CLASS (service);
468         g_return_val_if_fail (class->get_name != NULL, NULL);
469
470         return class->get_name (service, brief);
471 }
472
473 /**
474  * camel_service_get_path:
475  * @service: a #CamelService object
476  *
477  * This gets a valid UNIX relative path describing @service, which
478  * is guaranteed to be different from the path returned for any
479  * different service. This path MUST start with the name of the
480  * provider, followed by a "/", but after that, it is up to the
481  * provider.
482  *
483  * Returns: the path, which the caller must free
484  **/
485 gchar *
486 camel_service_get_path (CamelService *service)
487 {
488         CamelServiceClass *class;
489
490         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
491         g_return_val_if_fail (service->url, NULL);
492
493         class = CAMEL_SERVICE_GET_CLASS (service);
494         g_return_val_if_fail (class->get_path != NULL, NULL);
495
496         return class->get_path (service);
497 }
498
499 /**
500  * camel_service_get_session:
501  * @service: a #CamelService object
502  *
503  * Gets the #CamelSession associated with the service.
504  *
505  * Returns: the session
506  **/
507 CamelSession *
508 camel_service_get_session (CamelService *service)
509 {
510         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
511
512         return service->session;
513 }
514
515 /**
516  * camel_service_get_provider:
517  * @service: a #CamelService object
518  *
519  * Gets the #CamelProvider associated with the service.
520  *
521  * Returns: the provider
522  **/
523 CamelProvider *
524 camel_service_get_provider (CamelService *service)
525 {
526         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
527
528         return service->provider;
529 }
530
531 /**
532  * camel_service_query_auth_types:
533  * @service: a #CamelService object
534  * @error: return location for a #GError, or %NULL
535  *
536  * This is used by the mail source wizard to get the list of
537  * authentication types supported by the protocol, and information
538  * about them.
539  *
540  * Returns: a list of #CamelServiceAuthType records. The caller
541  * must free the list with #g_list_free when it is done with it.
542  **/
543 GList *
544 camel_service_query_auth_types (CamelService *service,
545                                 GError **error)
546 {
547         CamelServiceClass *class;
548         GList *ret;
549
550         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
551
552         class = CAMEL_SERVICE_GET_CLASS (service);
553         g_return_val_if_fail (class->query_auth_types != NULL, NULL);
554
555         /* Note that we get the connect lock here, which means the
556          * callee must not call the connect functions itself. */
557         camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
558         ret = class->query_auth_types (service, error);
559         camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
560
561         return ret;
562 }
563
564 /**
565  * camel_service_lock:
566  * @service: a #CamelService
567  * @lock: lock type to lock
568  *
569  * Locks #service's #lock. Unlock it with camel_service_unlock().
570  *
571  * Since: 2.32
572  **/
573 void
574 camel_service_lock (CamelService *service,
575                     CamelServiceLock lock)
576 {
577         g_return_if_fail (CAMEL_IS_SERVICE (service));
578
579         switch (lock) {
580                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
581                         g_static_rec_mutex_lock (&service->priv->connect_lock);
582                         break;
583                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
584                         g_static_mutex_lock (&service->priv->connect_op_lock);
585                         break;
586                 default:
587                         g_return_if_reached ();
588         }
589 }
590
591 /**
592  * camel_service_unlock:
593  * @service: a #CamelService
594  * @lock: lock type to unlock
595  *
596  * Unlocks #service's #lock, previously locked with camel_service_lock().
597  *
598  * Since: 2.32
599  **/
600 void
601 camel_service_unlock (CamelService *service,
602                       CamelServiceLock lock)
603 {
604         g_return_if_fail (CAMEL_IS_SERVICE (service));
605
606         switch (lock) {
607                 case CAMEL_SERVICE_REC_CONNECT_LOCK:
608                         g_static_rec_mutex_unlock (&service->priv->connect_lock);
609                         break;
610                 case CAMEL_SERVICE_CONNECT_OP_LOCK:
611                         g_static_mutex_unlock (&service->priv->connect_op_lock);
612                         break;
613                 default:
614                         g_return_if_reached ();
615         }
616 }