/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-session.c : Abstract class for an email session
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Dan Winship
* Jeffrey Stedfast
* Bertrand Guiheneuf
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include "camel-debug.h"
#include "camel-enumtypes.h"
#include "camel-file-utils.h"
#include "camel-folder.h"
#include "camel-mime-message.h"
#include "camel-sasl.h"
#include "camel-session.h"
#include "camel-store.h"
#include "camel-string-utils.h"
#include "camel-transport.h"
#include "camel-url.h"
#define CAMEL_SESSION_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_SESSION, CamelSessionPrivate))
/* Prioritize ahead of GTK+ redraws. */
#define JOB_PRIORITY G_PRIORITY_HIGH_IDLE
#define d(x)
typedef struct _AsyncContext AsyncContext;
typedef struct _SignalClosure SignalClosure;
typedef struct _JobData JobData;
struct _CamelSessionPrivate {
gchar *user_data_dir;
gchar *user_cache_dir;
GHashTable *services;
GMutex services_lock;
GHashTable *junk_headers;
CamelJunkFilter *junk_filter;
GMainContext *main_context;
guint online : 1;
};
struct _AsyncContext {
CamelFolder *folder;
CamelMimeMessage *message;
CamelService *service;
gchar *address;
gchar *auth_mechanism;
};
struct _SignalClosure {
GWeakRef session;
CamelService *service;
CamelSessionAlertType alert_type;
gchar *alert_message;
};
struct _JobData {
CamelSession *session;
GCancellable *cancellable;
CamelSessionCallback callback;
gpointer user_data;
GDestroyNotify notify;
GMainContext *main_context;
GError *error;
};
enum {
PROP_0,
PROP_JUNK_FILTER,
PROP_MAIN_CONTEXT,
PROP_ONLINE,
PROP_USER_DATA_DIR,
PROP_USER_CACHE_DIR
};
enum {
JOB_STARTED,
JOB_FINISHED,
USER_ALERT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE (CamelSession, camel_session, G_TYPE_OBJECT)
static void
async_context_free (AsyncContext *async_context)
{
if (async_context->folder != NULL)
g_object_unref (async_context->folder);
if (async_context->message != NULL)
g_object_unref (async_context->message);
if (async_context->service != NULL)
g_object_unref (async_context->service);
g_free (async_context->address);
g_free (async_context->auth_mechanism);
g_slice_free (AsyncContext, async_context);
}
static void
signal_closure_free (SignalClosure *signal_closure)
{
g_weak_ref_clear (&signal_closure->session);
if (signal_closure->service != NULL)
g_object_unref (signal_closure->service);
g_free (signal_closure->alert_message);
g_slice_free (SignalClosure, signal_closure);
}
static void
job_data_free (JobData *job_data)
{
camel_operation_pop_message (job_data->cancellable);
g_object_unref (job_data->session);
g_object_unref (job_data->cancellable);
g_clear_error (&job_data->error);
if (job_data->main_context)
g_main_context_unref (job_data->main_context);
if (job_data->notify != NULL)
job_data->notify (job_data->user_data);
g_slice_free (JobData, job_data);
}
static gboolean
session_finish_job_cb (gpointer user_data)
{
JobData *job_data = (JobData *) user_data;
g_return_val_if_fail (job_data != NULL, FALSE);
g_signal_emit (
job_data->session,
signals[JOB_FINISHED], 0,
job_data->cancellable, job_data->error);
return FALSE;
}
static void
session_job_thread (gpointer data,
gpointer user_data)
{
JobData *job_data = (JobData *) data;
GSource *source;
g_return_if_fail (job_data != NULL);
job_data->callback (
job_data->session,
job_data->cancellable,
job_data->user_data,
&job_data->error);
source = g_idle_source_new ();
g_source_set_priority (source, G_PRIORITY_DEFAULT);
g_source_set_callback (source, session_finish_job_cb, job_data, (GDestroyNotify) job_data_free);
g_source_attach (source, job_data->main_context);
g_source_unref (source);
}
static gboolean
session_start_job_cb (gpointer user_data)
{
static GThreadPool *job_pool = NULL;
static GMutex job_pool_mutex;
JobData *job_data = user_data;
g_signal_emit (
job_data->session,
signals[JOB_STARTED], 0,
job_data->cancellable);
g_mutex_lock (&job_pool_mutex);
if (!job_pool)
job_pool = g_thread_pool_new (session_job_thread, NULL, 20, FALSE, NULL);
job_data->main_context = g_main_context_ref_thread_default ();
g_thread_pool_push (job_pool, job_data, NULL);
g_mutex_unlock (&job_pool_mutex);
return FALSE;
}
static gboolean
session_emit_user_alert_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelSession *session;
session = g_weak_ref_get (&signal_closure->session);
if (session != NULL) {
g_signal_emit (
session,
signals[USER_ALERT], 0,
signal_closure->service,
signal_closure->alert_type,
signal_closure->alert_message);
g_object_unref (session);
}
return G_SOURCE_REMOVE;
}
static void
session_set_user_data_dir (CamelSession *session,
const gchar *user_data_dir)
{
g_return_if_fail (user_data_dir != NULL);
g_return_if_fail (session->priv->user_data_dir == NULL);
session->priv->user_data_dir = g_strdup (user_data_dir);
}
static void
session_set_user_cache_dir (CamelSession *session,
const gchar *user_cache_dir)
{
g_return_if_fail (user_cache_dir != NULL);
g_return_if_fail (session->priv->user_cache_dir == NULL);
session->priv->user_cache_dir = g_strdup (user_cache_dir);
}
static void
session_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_JUNK_FILTER:
camel_session_set_junk_filter (
CAMEL_SESSION (object),
g_value_get_object (value));
return;
case PROP_ONLINE:
camel_session_set_online (
CAMEL_SESSION (object),
g_value_get_boolean (value));
return;
case PROP_USER_DATA_DIR:
session_set_user_data_dir (
CAMEL_SESSION (object),
g_value_get_string (value));
return;
case PROP_USER_CACHE_DIR:
session_set_user_cache_dir (
CAMEL_SESSION (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
session_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_JUNK_FILTER:
g_value_set_object (
value, camel_session_get_junk_filter (
CAMEL_SESSION (object)));
return;
case PROP_MAIN_CONTEXT:
g_value_take_boxed (
value, camel_session_ref_main_context (
CAMEL_SESSION (object)));
return;
case PROP_ONLINE:
g_value_set_boolean (
value, camel_session_get_online (
CAMEL_SESSION (object)));
return;
case PROP_USER_DATA_DIR:
g_value_set_string (
value, camel_session_get_user_data_dir (
CAMEL_SESSION (object)));
return;
case PROP_USER_CACHE_DIR:
g_value_set_string (
value, camel_session_get_user_cache_dir (
CAMEL_SESSION (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
session_dispose (GObject *object)
{
CamelSessionPrivate *priv;
priv = CAMEL_SESSION_GET_PRIVATE (object);
g_hash_table_remove_all (priv->services);
if (priv->junk_filter != NULL) {
g_object_unref (priv->junk_filter);
priv->junk_filter = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (camel_session_parent_class)->dispose (object);
}
static void
session_finalize (GObject *object)
{
CamelSessionPrivate *priv;
priv = CAMEL_SESSION_GET_PRIVATE (object);
g_free (priv->user_data_dir);
g_free (priv->user_cache_dir);
g_hash_table_destroy (priv->services);
if (priv->main_context != NULL)
g_main_context_unref (priv->main_context);
g_mutex_clear (&priv->services_lock);
if (priv->junk_headers) {
g_hash_table_remove_all (priv->junk_headers);
g_hash_table_destroy (priv->junk_headers);
}
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_session_parent_class)->finalize (object);
}
static CamelService *
session_add_service (CamelSession *session,
const gchar *uid,
const gchar *protocol,
CamelProviderType type,
GError **error)
{
CamelService *service;
CamelProvider *provider;
GType service_type = G_TYPE_INVALID;
service = camel_session_ref_service (session, uid);
if (CAMEL_IS_SERVICE (service))
return service;
/* Try to find a suitable CamelService subclass. */
provider = camel_provider_get (protocol, error);
if (provider != NULL)
service_type = provider->object_types[type];
if (error && *error)
return NULL;
if (service_type == G_TYPE_INVALID) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_URL_INVALID,
_("No provider available for protocol '%s'"),
protocol);
return NULL;
}
if (!g_type_is_a (service_type, CAMEL_TYPE_SERVICE)) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_INVALID,
_("Invalid GType registered for protocol '%s'"),
protocol);
return NULL;
}
service = g_initable_new (
service_type, NULL, error,
"provider", provider, "session",
session, "uid", uid, NULL);
if (service != NULL) {
g_mutex_lock (&session->priv->services_lock);
g_hash_table_insert (
session->priv->services,
g_strdup (uid),
g_object_ref (service));
g_mutex_unlock (&session->priv->services_lock);
}
return service;
}
static void
session_remove_service (CamelSession *session,
CamelService *service)
{
const gchar *uid;
g_mutex_lock (&session->priv->services_lock);
uid = camel_service_get_uid (service);
g_hash_table_remove (session->priv->services, uid);
g_mutex_unlock (&session->priv->services_lock);
}
static gboolean
session_authenticate_sync (CamelSession *session,
CamelService *service,
const gchar *mechanism,
GCancellable *cancellable,
GError **error)
{
CamelServiceAuthType *authtype = NULL;
CamelAuthenticationResult result;
GError *local_error = NULL;
/* XXX This authenticate_sync() implementation serves only as
* a rough example and is not intended to be used as is.
*
* Any CamelSession subclass should override this method
* and implement a more complete authentication loop that
* handles user prompts and password storage.
*/
g_warning (
"The default CamelSession.authenticate_sync() "
"method is not intended for production use.");
/* If a SASL mechanism was given and we can't find
* a CamelServiceAuthType for it, fail immediately. */
if (mechanism != NULL) {
authtype = camel_sasl_authtype (mechanism);
if (authtype == NULL) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("No support for %s authentication"),
mechanism);
return FALSE;
}
}
/* If the SASL mechanism does not involve a user
* password, then it gets one shot to authenticate. */
if (authtype != NULL && !authtype->need_password) {
result = camel_service_authenticate_sync (
service, mechanism, cancellable, error);
if (result == CAMEL_AUTHENTICATION_REJECTED)
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("%s authentication failed"), mechanism);
return (result == CAMEL_AUTHENTICATION_ACCEPTED);
}
/* Some SASL mechanisms can attempt to authenticate without a
* user password being provided (e.g. single-sign-on credentials),
* but can fall back to a user password. Handle that case next. */
if (mechanism != NULL) {
CamelProvider *provider;
CamelSasl *sasl;
const gchar *service_name;
gboolean success = FALSE;
provider = camel_service_get_provider (service);
service_name = provider->protocol;
/* XXX Would be nice if camel_sasl_try_empty_password_sync()
* returned CamelAuthenticationResult so it's easier to
* detect errors. */
sasl = camel_sasl_new (service_name, mechanism, service);
if (sasl != NULL) {
success = camel_sasl_try_empty_password_sync (
sasl, cancellable, &local_error);
g_object_unref (sasl);
}
if (success)
return TRUE;
}
/* Abort authentication if we got cancelled.
* Otherwise clear any errors and press on. */
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return FALSE;
g_clear_error (&local_error);
retry:
/* XXX This is where things get bogus. In a real implementation you
* would want to fetch a stored password or prompt the user here.
* Password should be stashed using camel_service_set_password()
* before calling camel_service_authenticate_sync(). */
result = camel_service_authenticate_sync (
service, mechanism, cancellable, error);
if (result == CAMEL_AUTHENTICATION_REJECTED) {
/* XXX Request a different password here. */
goto retry;
}
if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
/* XXX Possibly store the password here using
* GNOME Keyring or something equivalent. */
}
return (result == CAMEL_AUTHENTICATION_ACCEPTED);
}
static gboolean
session_forward_to_sync (CamelSession *session,
CamelFolder *folder,
CamelMimeMessage *message,
const gchar *address,
GCancellable *cancellable,
GError **error)
{
g_set_error_literal (
error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Forwarding messages is not supported"));
return FALSE;
}
static void
camel_session_class_init (CamelSessionClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelSessionPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = session_set_property;
object_class->get_property = session_get_property;
object_class->dispose = session_dispose;
object_class->finalize = session_finalize;
class->add_service = session_add_service;
class->remove_service = session_remove_service;
class->authenticate_sync = session_authenticate_sync;
class->forward_to_sync = session_forward_to_sync;
g_object_class_install_property (
object_class,
PROP_JUNK_FILTER,
g_param_spec_object (
"junk-filter",
"Junk Filter",
"Classifies messages as junk or not junk",
CAMEL_TYPE_JUNK_FILTER,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_MAIN_CONTEXT,
g_param_spec_boxed (
"main-context",
"Main Context",
"The main loop context on "
"which to attach event sources",
G_TYPE_MAIN_CONTEXT,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_ONLINE,
g_param_spec_boolean (
"online",
"Online",
"Whether the shell is online",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_USER_DATA_DIR,
g_param_spec_string (
"user-data-dir",
"User Data Directory",
"User-specific base directory for mail data",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_USER_CACHE_DIR,
g_param_spec_string (
"user-cache-dir",
"User Cache Directory",
"User-specific base directory for mail cache",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
signals[JOB_STARTED] = g_signal_new (
"job-started",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CamelSessionClass, job_started),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_CANCELLABLE);
signals[JOB_FINISHED] = g_signal_new (
"job-finished",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CamelSessionClass, job_finished),
NULL, NULL, NULL,
G_TYPE_NONE, 2,
G_TYPE_CANCELLABLE,
G_TYPE_POINTER);
/**
* CamelSession::user-alert:
* @session: the #CamelSession that received the signal
* @service: the #CamelService issuing the alert
* @type: the #CamelSessionAlertType
* @message: the alert message
*
* This purpose of this signal is to propagate a server-issued alert
* message from @service to a user interface. The @type hints at the
* severity of the alert message.
**/
signals[USER_ALERT] = g_signal_new (
"user-alert",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CamelSessionClass, user_alert),
NULL, NULL, NULL,
G_TYPE_NONE, 3,
CAMEL_TYPE_SERVICE,
CAMEL_TYPE_SESSION_ALERT_TYPE,
G_TYPE_STRING);
}
static void
camel_session_init (CamelSession *session)
{
GHashTable *services;
services = g_hash_table_new_full (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
session->priv = CAMEL_SESSION_GET_PRIVATE (session);
session->priv->services = services;
g_mutex_init (&session->priv->services_lock);
session->priv->junk_headers = NULL;
session->priv->main_context = g_main_context_ref_thread_default ();
}
/**
* camel_session_ref_main_context:
* @session: a #CamelSession
*
* Returns the #GMainContext on which event sources for @session are to
* be attached.
*
* Returns: a #GMainContext
*
* Since: 3.8
**/
GMainContext *
camel_session_ref_main_context (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return g_main_context_ref (session->priv->main_context);
}
/**
* camel_session_get_user_data_dir:
* @session: a #CamelSession
*
* Returns the base directory under which to store user-specific mail data.
*
* Returns: the base directory for mail data
*
* Since: 3.2
**/
const gchar *
camel_session_get_user_data_dir (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return session->priv->user_data_dir;
}
/**
* camel_session_get_user_cache_dir:
* @session: a #CamelSession
*
* Returns the base directory under which to store user-specific mail cache.
*
* Returns: the base directory for mail cache
*
* Since: 3.4
**/
const gchar *
camel_session_get_user_cache_dir (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return session->priv->user_cache_dir;
}
/**
* camel_session_add_service:
* @session: a #CamelSession
* @uid: a unique identifier string
* @protocol: the service protocol
* @type: the service type
* @error: return location for a #GError, or %NULL
*
* Instantiates a new #CamelService for @session. The @uid identifies the
* service for future lookup. The @protocol indicates which #CamelProvider
* holds the #GType of the #CamelService subclass to instantiate. The @type
* explicitly designates the service as a #CamelStore or #CamelTransport.
*
* If the given @uid has already been added, the existing #CamelService
* with that @uid is returned regardless of whether it agrees with the
* given @protocol and @type.
*
* If no #CamelProvider is available to handle the given @protocol, or
* if the #CamelProvider does not specify a valid #GType for @type, the
* function sets @error and returns %NULL.
*
* The returned #CamelService is referenced for thread-safety and must be
* unreferenced with g_object_unref() when finished with it.
*
* Returns: a #CamelService instance, or %NULL
*
* Since: 3.2
**/
CamelService *
camel_session_add_service (CamelSession *session,
const gchar *uid,
const gchar *protocol,
CamelProviderType type,
GError **error)
{
CamelSessionClass *class;
CamelService *service;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_return_val_if_fail (uid != NULL, NULL);
g_return_val_if_fail (protocol != NULL, NULL);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->add_service != NULL, NULL);
service = class->add_service (session, uid, protocol, type, error);
CAMEL_CHECK_GERROR (session, add_service, service != NULL, error);
return service;
}
/**
* camel_session_remove_service:
* @session: a #CamelSession
* @service: the #CamelService to remove
*
* Removes a #CamelService previously added by camel_session_add_service().
*
* Since: 3.2
**/
void
camel_session_remove_service (CamelSession *session,
CamelService *service)
{
CamelSessionClass *class;
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (CAMEL_IS_SERVICE (service));
class = CAMEL_SESSION_GET_CLASS (session);
g_return_if_fail (class->remove_service != NULL);
class->remove_service (session, service);
}
/**
* camel_session_ref_service:
* @session: a #CamelSession
* @uid: a unique identifier string
*
* Looks up a #CamelService by its unique identifier string. The service
* must have been previously added using camel_session_add_service().
*
* The returned #CamelService is referenced for thread-safety and must be
* unreferenced with g_object_unref() when finished with it.
*
* Returns: a #CamelService instance, or %NULL
*
* Since: 3.6
**/
CamelService *
camel_session_ref_service (CamelSession *session,
const gchar *uid)
{
CamelService *service;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_return_val_if_fail (uid != NULL, NULL);
g_mutex_lock (&session->priv->services_lock);
service = g_hash_table_lookup (session->priv->services, uid);
if (service != NULL)
g_object_ref (service);
g_mutex_unlock (&session->priv->services_lock);
return service;
}
/**
* camel_session_ref_service_by_url:
* @session: a #CamelSession
* @url: a #CamelURL
* @type: a #CamelProviderType
*
* Looks up a #CamelService by trying to match its #CamelURL against the
* given @url and then checking that the object is of the desired @type.
* The service must have been previously added using
* camel_session_add_service().
*
* The returned #CamelService is referenced for thread-safety and must be
* unreferenced with g_object_unref() when finished with it.
*
* Note this function is significantly slower than camel_session_ref_service().
*
* Returns: a #CamelService instance, or %NULL
*
* Since: 3.6
**/
CamelService *
camel_session_ref_service_by_url (CamelSession *session,
CamelURL *url,
CamelProviderType type)
{
CamelService *match = NULL;
GList *list, *iter;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_return_val_if_fail (url != NULL, NULL);
list = camel_session_list_services (session);
for (iter = list; iter != NULL; iter = g_list_next (iter)) {
CamelProvider *provider;
CamelService *service;
CamelURL *service_url;
gboolean url_equal;
service = CAMEL_SERVICE (iter->data);
provider = camel_service_get_provider (service);
if (provider == NULL)
continue;
if (provider->url_equal == NULL)
continue;
service_url = camel_service_new_camel_url (service);
url_equal = provider->url_equal (url, service_url);
camel_url_free (service_url);
if (!url_equal)
continue;
switch (type) {
case CAMEL_PROVIDER_STORE:
if (CAMEL_IS_STORE (service))
match = g_object_ref (service);
break;
case CAMEL_PROVIDER_TRANSPORT:
if (CAMEL_IS_TRANSPORT (service))
match = g_object_ref (service);
break;
default:
g_warn_if_reached ();
break;
}
if (match != NULL)
break;
}
g_list_free_full (list, (GDestroyNotify) g_object_unref);
return match;
}
/**
* camel_session_list_services:
* @session: a #CamelSession
*
* Returns a list of all #CamelService objects previously added using
* camel_session_add_service().
*
* The services returned in the list are referenced for thread-safety.
* They must each be unreferenced with g_object_unref() when finished
* with them. Free the returned list itself with g_list_free().
*
* An easy way to free the list property in one step is as follows:
*
* |[
* g_list_free_full (list, g_object_unref);
* ]|
*
* Returns: an unsorted list of #CamelService objects
*
* Since: 3.2
**/
GList *
camel_session_list_services (CamelSession *session)
{
GList *list;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_mutex_lock (&session->priv->services_lock);
list = g_hash_table_get_values (session->priv->services);
g_list_foreach (list, (GFunc) g_object_ref, NULL);
g_mutex_unlock (&session->priv->services_lock);
return list;
}
/**
* camel_session_remove_services:
* @session: a #CamelSession
*
* Removes all #CamelService instances added by camel_session_add_service().
*
* This can be useful during application shutdown to ensure all #CamelService
* instances are freed properly, especially since #CamelSession instances are
* prone to reference cycles.
*
* Since: 3.2
**/
void
camel_session_remove_services (CamelSession *session)
{
g_return_if_fail (CAMEL_IS_SESSION (session));
g_mutex_lock (&session->priv->services_lock);
g_hash_table_remove_all (session->priv->services);
g_mutex_unlock (&session->priv->services_lock);
}
/**
* camel_session_get_password:
* @session: a #CamelSession
* @service: the #CamelService this query is being made by
* @prompt: prompt to provide to user
* @item: an identifier, unique within this service, for the information
* @flags: %CAMEL_SESSION_PASSWORD_REPROMPT, the prompt should force a reprompt
* %CAMEL_SESSION_PASSWORD_SECRET, whether the password is secret
* %CAMEL_SESSION_PASSWORD_STATIC, the password is remembered externally
* @error: return location for a #GError, or %NULL
*
* This function is used by a #CamelService to ask the application and
* the user for a password or other authentication data.
*
* @service and @item together uniquely identify the piece of data the
* caller is concerned with.
*
* @prompt is a question to ask the user (if the application doesn't
* already have the answer cached). If %CAMEL_SESSION_PASSWORD_SECRET
* is set, the user's input will not be echoed back.
*
* If %CAMEL_SESSION_PASSWORD_STATIC is set, it means the password returned
* will be stored statically by the caller automatically, for the current
* session.
*
* The authenticator should set @error to %G_IO_ERROR_CANCELLED if
* the user did not provide the information. The caller must g_free()
* the information returned when it is done with it.
*
* Returns: the authentication information or %NULL
**/
gchar *
camel_session_get_password (CamelSession *session,
CamelService *service,
const gchar *prompt,
const gchar *item,
guint32 flags,
GError **error)
{
CamelSessionClass *class;
gchar *password;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_return_val_if_fail (prompt != NULL, NULL);
g_return_val_if_fail (item != NULL, NULL);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->get_password != NULL, NULL);
password = class->get_password (
session, service, prompt, item, flags, error);
CAMEL_CHECK_GERROR (session, get_password, password != NULL, error);
return password;
}
/**
* camel_session_forget_password:
* @session: a #CamelSession
* @service: the #CamelService rejecting the password
* @item: an identifier, unique within this service, for the information
* @error: return location for a #GError, or %NULL
*
* This function is used by a #CamelService to tell the application
* that the authentication information it provided via
* camel_session_get_password() was rejected by the service. If the
* application was caching this information, it should stop,
* and if the service asks for it again, it should ask the user.
*
* @service and @item identify the rejected authentication information,
* as with camel_session_get_password().
*
* Returns: %TRUE on success, %FALSE on failure
**/
gboolean
camel_session_forget_password (CamelSession *session,
CamelService *service,
const gchar *item,
GError **error)
{
CamelSessionClass *class;
gboolean success;
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (item != NULL, FALSE);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->forget_password, FALSE);
success = class->forget_password (session, service, item, error);
CAMEL_CHECK_GERROR (session, forget_password, success, error);
return success;
}
/**
* camel_session_trust_prompt:
* @session: a #CamelSession
* @service: a #CamelService
* @certificate: the peer's #GTlsCertificate
* @errors: the problems with @certificate
*
* Prompts the user whether to accept @certificate for @service. The
* set of flags given in @errors indicate why the @certificate failed
* validation.
*
* If an error occurs during prompting or if the user declines to respond,
* the function returns #CAMEL_CERT_TRUST_UNKNOWN and the certificate will
* be rejected.
*
* Returns: the user's trust level for @certificate
*
* Since: 3.8
**/
CamelCertTrust
camel_session_trust_prompt (CamelSession *session,
CamelService *service,
GTlsCertificate *certificate,
GTlsCertificateFlags errors)
{
CamelSessionClass *class;
g_return_val_if_fail (
CAMEL_IS_SESSION (session), CAMEL_CERT_TRUST_UNKNOWN);
g_return_val_if_fail (
CAMEL_IS_SERVICE (service), CAMEL_CERT_TRUST_UNKNOWN);
g_return_val_if_fail (
G_IS_TLS_CERTIFICATE (certificate), CAMEL_CERT_TRUST_UNKNOWN);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (
class->trust_prompt != NULL, CAMEL_CERT_TRUST_UNKNOWN);
return class->trust_prompt (session, service, certificate, errors);
}
/**
* camel_session_user_alert:
* @session: a #CamelSession
* @service: a #CamelService
* @type: a #CamelSessionAlertType
* @message: the message for the user
*
* Emits a #CamelSession:user_alert signal from an idle source on the main
* loop. The idle source's priority is #G_PRIORITY_LOW.
*
* The purpose of the signal is to propagate a server-issued alert message
* from @service to a user interface. The @type hints at the nature of the
* alert message.
*
* Since: 3.12
*/
void
camel_session_user_alert (CamelSession *session,
CamelService *service,
CamelSessionAlertType type,
const gchar *message)
{
SignalClosure *signal_closure;
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (CAMEL_IS_SERVICE (service));
g_return_if_fail (message != NULL);
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->session, session);
signal_closure->service = g_object_ref (service);
signal_closure->alert_type = type;
signal_closure->alert_message = g_strdup (message);
camel_session_idle_add (
session, G_PRIORITY_LOW,
session_emit_user_alert_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
}
/**
* camel_session_lookup_addressbook:
*
* Since: 2.22
**/
gboolean
camel_session_lookup_addressbook (CamelSession *session,
const gchar *name)
{
CamelSessionClass *class;
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->lookup_addressbook != NULL, FALSE);
return class->lookup_addressbook (session, name);
}
/**
* camel_session_get_online:
* @session: a #CamelSession
*
* Returns: whether or not @session is online
**/
gboolean
camel_session_get_online (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
return session->priv->online;
}
/**
* camel_session_set_online:
* @session: a #CamelSession
* @online: whether or not the session should be online
*
* Sets the online status of @session to @online.
**/
void
camel_session_set_online (CamelSession *session,
gboolean online)
{
g_return_if_fail (CAMEL_IS_SESSION (session));
if (online == session->priv->online)
return;
session->priv->online = online;
g_object_notify (G_OBJECT (session), "online");
}
/**
* camel_session_get_filter_driver:
* @session: a #CamelSession
* @type: the type of filter (eg, "incoming")
* @error: return location for a #GError, or %NULL
*
* Returns: a filter driver, loaded with applicable rules
**/
CamelFilterDriver *
camel_session_get_filter_driver (CamelSession *session,
const gchar *type,
GError **error)
{
CamelSessionClass *class;
CamelFilterDriver *driver;
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
g_return_val_if_fail (type != NULL, NULL);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->get_filter_driver != NULL, NULL);
driver = class->get_filter_driver (session, type, error);
CAMEL_CHECK_GERROR (session, get_filter_driver, driver != NULL, error);
return driver;
}
/**
* camel_session_get_junk_filter:
* @session: a #CamelSession
*
* Returns the #CamelJunkFilter instance used to classify messages as
* junk or not junk during filtering.
*
* Note that #CamelJunkFilter itself is just an interface. The application
* must implement the interface and install a #CamelJunkFilter instance for
* junk filtering to take place.
*
* Returns: a #CamelJunkFilter, or %NULL
*
* Since: 3.2
**/
CamelJunkFilter *
camel_session_get_junk_filter (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return session->priv->junk_filter;
}
/**
* camel_session_set_junk_filter:
* @session: a #CamelSession
* @junk_filter: a #CamelJunkFilter, or %NULL
*
* Installs the #CamelJunkFilter instance used to classify messages as
* junk or not junk during filtering.
*
* Note that #CamelJunkFilter itself is just an interface. The application
* must implement the interface and install a #CamelJunkFilter instance for
* junk filtering to take place.
*
* Since: 3.2
**/
void
camel_session_set_junk_filter (CamelSession *session,
CamelJunkFilter *junk_filter)
{
g_return_if_fail (CAMEL_IS_SESSION (session));
if (junk_filter != NULL) {
g_return_if_fail (CAMEL_IS_JUNK_FILTER (junk_filter));
g_object_ref (junk_filter);
}
if (session->priv->junk_filter != NULL)
g_object_unref (session->priv->junk_filter);
session->priv->junk_filter = junk_filter;
g_object_notify (G_OBJECT (session), "junk-filter");
}
/**
* camel_session_idle_add:
* @session: a #CamelSession
* @priority: the priority of the idle source
* @function: a function to call
* @data: data to pass to @function
* @notify: function to call when the idle is removed, or %NULL
*
* Adds a function to be called whenever there are no higher priority events
* pending. If @function returns %FALSE it is automatically removed from the
* list of event sources and will not be called again.
*
* This internally creates a main loop source using g_idle_source_new()
* and attaches it to @session's own #CamelSession:main-context using
* g_source_attach().
*
* The @priority is typically in the range between %G_PRIORITY_DEFAULT_IDLE
* and %G_PRIORITY_HIGH_IDLE.
*
* Returns: the ID (greater than 0) of the event source
*
* Since: 3.6
**/
guint
camel_session_idle_add (CamelSession *session,
gint priority,
GSourceFunc function,
gpointer data,
GDestroyNotify notify)
{
GMainContext *main_context;
GSource *source;
guint source_id;
g_return_val_if_fail (CAMEL_IS_SESSION (session), 0);
g_return_val_if_fail (function != NULL, 0);
main_context = camel_session_ref_main_context (session);
source = g_idle_source_new ();
if (priority != G_PRIORITY_DEFAULT_IDLE)
g_source_set_priority (source, priority);
g_source_set_callback (source, function, data, notify);
source_id = g_source_attach (source, main_context);
g_source_unref (source);
g_main_context_unref (main_context);
return source_id;
}
/**
* camel_session_submit_job:
* @session: a #CamelSession
* @description: human readable description of the job, shown to a user
* @callback: a #CamelSessionCallback
* @user_data: user data passed to the callback
* @notify: a #GDestroyNotify function
*
* This function provides a simple mechanism for providers to initiate
* low-priority background jobs. Jobs can be submitted from any thread,
* but execution of the jobs is always as follows:
*
* 1) The #CamelSession:job-started signal is emitted from the thread
* in which @session was created. This is typically the same thread
* that hosts the global default #GMainContext, or "main" thread.
*
* 2) The @callback function is invoked from a different thread where
* it's safe to call synchronous functions.
*
* 3) Once @callback has returned, the #CamelSesson:job-finished signal
* is emitted from the same thread as #CamelSession:job-started was
* emitted.
*
* 4) Finally if a @notify function was provided, it is invoked and
* passed @user_data so that @user_data can be freed.
*
* Since: 3.2
**/
void
camel_session_submit_job (CamelSession *session,
const gchar *description,
CamelSessionCallback callback,
gpointer user_data,
GDestroyNotify notify)
{
JobData *job_data;
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (description != NULL);
g_return_if_fail (callback != NULL);
job_data = g_slice_new0 (JobData);
job_data->session = g_object_ref (session);
job_data->cancellable = camel_operation_new ();
job_data->callback = callback;
job_data->user_data = user_data;
job_data->notify = notify;
job_data->main_context = NULL;
job_data->error = NULL;
camel_operation_push_message (job_data->cancellable, "%s", description);
camel_session_idle_add (
session, JOB_PRIORITY,
session_start_job_cb,
job_data, (GDestroyNotify) NULL);
}
/**
* camel_session_set_junk_headers:
*
* Since: 2.22
**/
void
camel_session_set_junk_headers (CamelSession *session,
const gchar **headers,
const gchar **values,
gint len)
{
gint i;
g_return_if_fail (CAMEL_IS_SESSION (session));
if (session->priv->junk_headers) {
g_hash_table_remove_all (session->priv->junk_headers);
g_hash_table_destroy (session->priv->junk_headers);
}
session->priv->junk_headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
for (i = 0; i < len; i++) {
g_hash_table_insert (session->priv->junk_headers, g_strdup (headers[i]), g_strdup (values[i]));
}
}
/**
* camel_session_get_junk_headers:
*
* Since: 2.22
**/
const GHashTable *
camel_session_get_junk_headers (CamelSession *session)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return session->priv->junk_headers;
}
/**
* camel_session_authenticate_sync:
* @session: a #CamelSession
* @service: a #CamelService
* @mechanism: a SASL mechanism name, or %NULL
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Authenticates @service, which may involve repeated calls to
* camel_service_authenticate() or camel_service_authenticate_sync().
* A #CamelSession subclass is largely responsible for implementing this,
* and should handle things like user prompts and secure password storage.
* These issues are out-of-scope for Camel.
*
* If an error occurs, or if authentication is aborted, the function sets
* @error and returns %FALSE.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.4
**/
gboolean
camel_session_authenticate_sync (CamelSession *session,
CamelService *service,
const gchar *mechanism,
GCancellable *cancellable,
GError **error)
{
CamelSessionClass *class;
gboolean success;
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->authenticate_sync != NULL, FALSE);
success = class->authenticate_sync (
session, service, mechanism, cancellable, error);
CAMEL_CHECK_GERROR (session, authenticate_sync, success, error);
return success;
}
/* Helper for camel_session_authenticate() */
static void
session_authenticate_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
AsyncContext *async_context;
GError *local_error = NULL;
async_context = (AsyncContext *) task_data;
success = camel_session_authenticate_sync (
CAMEL_SESSION (source_object),
async_context->service,
async_context->auth_mechanism,
cancellable, &local_error);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
g_task_return_boolean (task, success);
}
}
/**
* camel_session_authenticate:
* @session: a #CamelSession
* @service: a #CamelService
* @mechanism: a SASL mechanism name, or %NULL
* @io_priority: the I/O priority for the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously authenticates @service, which may involve repeated calls
* to camel_service_authenticate() or camel_service_authenticate_sync().
* A #CamelSession subclass is largely responsible for implementing this,
* and should handle things like user prompts and secure password storage.
* These issues are out-of-scope for Camel.
*
* When the operation is finished, @callback will be called. You can
* then call camel_session_authenticate_finish() to get the result of
* the operation.
*
* Since: 3.4
**/
void
camel_session_authenticate (CamelSession *session,
CamelService *service,
const gchar *mechanism,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (CAMEL_IS_SERVICE (service));
async_context = g_slice_new0 (AsyncContext);
async_context->service = g_object_ref (service);
async_context->auth_mechanism = g_strdup (mechanism);
task = g_task_new (session, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_session_authenticate);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
g_task_run_in_thread (task, session_authenticate_thread);
g_object_unref (task);
}
/**
* camel_session_authenticate_finish:
* @session: a #CamelSession
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_session_authenticate().
*
* If an error occurred, or if authentication was aborted, the function
* sets @error and returns %FALSE.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.4
**/
gboolean
camel_session_authenticate_finish (CamelSession *session,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_session_authenticate), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* camel_session_forward_to_sync:
* @session: a #CamelSession
* @folder: the #CamelFolder where @message is located
* @message: the #CamelMimeMessage to forward
* @address: the recipient's email address
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Forwards @message in @folder to the email address(es) given by @address.
*
* If an error occurs, the function sets @error and returns %FALSE.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.6
**/
gboolean
camel_session_forward_to_sync (CamelSession *session,
CamelFolder *folder,
CamelMimeMessage *message,
const gchar *address,
GCancellable *cancellable,
GError **error)
{
CamelSessionClass *class;
gboolean success;
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
g_return_val_if_fail (address != NULL, FALSE);
class = CAMEL_SESSION_GET_CLASS (session);
g_return_val_if_fail (class->forward_to_sync != NULL, FALSE);
success = class->forward_to_sync (
session, folder, message, address, cancellable, error);
CAMEL_CHECK_GERROR (session, forward_to_sync, success, error);
return success;
}
/* Helper for camel_session_forward_to() */
static void
session_forward_to_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
AsyncContext *async_context;
GError *local_error = NULL;
async_context = (AsyncContext *) task_data;
success = camel_session_forward_to_sync (
CAMEL_SESSION (source_object),
async_context->folder,
async_context->message,
async_context->address,
cancellable, &local_error);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
g_task_return_boolean (task, success);
}
}
/**
* camel_session_forward_to:
* @session: a #CamelSession
* @folder: the #CamelFolder where @message is located
* @message: the #CamelMimeMessage to forward
* @address: the recipient's email address
* @io_priority: the I/O priority for the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously forwards @message in @folder to the email address(s)
* given by @address.
*
* When the operation is finished, @callback will be called. You can
* then call camel_session_forward_to_finish() to get the result of the
* operation.
*
* Since: 3.6
**/
void
camel_session_forward_to (CamelSession *session,
CamelFolder *folder,
CamelMimeMessage *message,
const gchar *address,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (CAMEL_IS_FOLDER (folder));
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
g_return_if_fail (address != NULL);
async_context = g_slice_new0 (AsyncContext);
async_context->folder = g_object_ref (folder);
async_context->message = g_object_ref (message);
async_context->address = g_strdup (address);
task = g_task_new (session, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_session_forward_to);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
g_task_run_in_thread (task, session_forward_to_thread);
g_object_unref (task);
}
/**
* camel_session_forward_to_finish:
* @session: a #CamelSession
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_session_forward_to().
*
* If an error occurred, the function sets @error and returns %FALSE.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.6
**/
gboolean
camel_session_forward_to_finish (CamelSession *session,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_session_forward_to), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}