/*
* camel-imapx-list-response.c
*
* 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 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 .
*
*/
/**
* SECTION: camel-imapx-list-response
* @include: camel/camel.h
* @short_description: Stores an IMAP LIST response
*
* #CamelIMAPXListResponse encapsulates an IMAP LIST response, which consists
* of a set of mailbox attributes, a mailbox separator character, and the
* mailbox name. (Extended information for LIST responses, as described in
* RFC 5258, to be
* supported at a later date.)
*
* #CamelIMAPXListResponse can also convert the mailbox attributes to
* a #CamelStoreInfoFlags / #CamelFolderInfoFlags value for use with
* camel_store_get_folder_info().
**/
#include "camel-imapx-list-response.h"
#include "camel-imapx-utils.h"
#define CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_IMAPX_LIST_RESPONSE, CamelIMAPXListResponsePrivate))
struct _CamelIMAPXListResponsePrivate {
gchar *mailbox_name;
gchar separator;
GHashTable *attributes;
GHashTable *extended_items;
};
G_DEFINE_TYPE (
CamelIMAPXListResponse,
camel_imapx_list_response,
G_TYPE_OBJECT)
/* These are internalized on class initialization. */
static const gchar *known_attributes[] = {
CAMEL_IMAPX_LIST_ATTR_MARKED,
CAMEL_IMAPX_LIST_ATTR_NOINFERIORS,
CAMEL_IMAPX_LIST_ATTR_NOSELECT,
CAMEL_IMAPX_LIST_ATTR_UNMARKED,
CAMEL_IMAPX_LIST_ATTR_HASCHILDREN,
CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN,
CAMEL_IMAPX_LIST_ATTR_NONEXISTENT,
CAMEL_IMAPX_LIST_ATTR_REMOTE,
CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED,
CAMEL_IMAPX_LIST_ATTR_ALL,
CAMEL_IMAPX_LIST_ATTR_ARCHIVE,
CAMEL_IMAPX_LIST_ATTR_DRAFTS,
CAMEL_IMAPX_LIST_ATTR_FLAGGED,
CAMEL_IMAPX_LIST_ATTR_JUNK,
CAMEL_IMAPX_LIST_ATTR_SENT,
CAMEL_IMAPX_LIST_ATTR_TRASH
};
static void
imapx_list_response_finalize (GObject *object)
{
CamelIMAPXListResponsePrivate *priv;
priv = CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE (object);
g_free (priv->mailbox_name);
g_hash_table_destroy (priv->attributes);
g_hash_table_destroy (priv->extended_items);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_imapx_list_response_parent_class)->
finalize (object);
}
static void
camel_imapx_list_response_class_init (CamelIMAPXListResponseClass *class)
{
GObjectClass *object_class;
gint ii;
g_type_class_add_private (
class, sizeof (CamelIMAPXListResponsePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = imapx_list_response_finalize;
/* Internalize known mailbox attribute names. */
for (ii = 0; ii < G_N_ELEMENTS (known_attributes); ii++) {
const gchar *string = known_attributes[ii];
known_attributes[ii] = g_intern_static_string (string);
}
}
static void
camel_imapx_list_response_init (CamelIMAPXListResponse *response)
{
GHashTable *attributes;
GHashTable *extended_items;
/* Set of internalized attribute strings. */
attributes = g_hash_table_new (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal);
extended_items = g_hash_table_new_full (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_variant_unref);
response->priv = CAMEL_IMAPX_LIST_RESPONSE_GET_PRIVATE (response);
response->priv->attributes = attributes;
response->priv->extended_items = extended_items;
}
/* Helper for camel_imapx_list_response_new() */
static GVariant *
imapx_list_response_parse_childinfo (CamelIMAPXStream *stream,
CamelIMAPXListResponse *response,
GCancellable *cancellable,
GError **error)
{
GVariantBuilder builder;
GVariant *value;
camel_imapx_token_t tok;
guchar *token;
guint len;
g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != '(') {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list childinfo: expecting ')'");
goto fail;
}
repeat:
if (!camel_imapx_stream_astring (stream, &token, cancellable, error))
goto fail;
value = g_variant_new_string ((gchar *) token);
g_variant_builder_add_value (&builder, value);
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != ')') {
camel_imapx_stream_ungettoken (stream, tok, token, len);
goto repeat;
}
return g_variant_builder_end (&builder);
fail:
g_variant_builder_clear (&builder);
return NULL;
}
/* Helper for camel_imapx_list_response_new() */
static GVariant *
imapx_list_response_parse_oldname (CamelIMAPXStream *stream,
CamelIMAPXListResponse *response,
GCancellable *cancellable,
GError **error)
{
camel_imapx_token_t tok;
guchar *token;
guint len;
gchar *mailbox_name = NULL;
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != '(') {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list oldname: expecting ')'");
goto fail;
}
/* The separator character should be known by now. */
mailbox_name = camel_imapx_parse_mailbox (
stream, response->priv->separator, cancellable, error);
if (mailbox_name == NULL)
goto fail;
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != ')') {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list oldname: expecting ')'");
goto fail;
}
return g_variant_new_string (mailbox_name);
fail:
g_free (mailbox_name);
return NULL;
}
/* Helper for camel_imapx_list_response_new() */
static gboolean
imapx_list_response_parse_extended_item (CamelIMAPXStream *stream,
CamelIMAPXListResponse *response,
GCancellable *cancellable,
GError **error)
{
guchar *token;
gchar *item_tag;
GVariant *item_value = NULL;
gboolean success;
/* Parse the extended item tag. */
if (!camel_imapx_stream_astring (stream, &token, cancellable, error))
return FALSE;
item_tag = g_strdup ((gchar *) token);
/* Parse the extended item value if we recognize the tag,
* otherwise skip to the end. This is easier than trying
* to anticipate all possible extensions ahead of time.
*
* XXX If we had a real LALR(1) parser then we could at
* least parse it into an abstract syntax tree, and
* pick out the items we support. Alas, our ad-hoc
* IMAP parser makes this more difficult. */
/* RFC 5258 "LIST-EXTENDED" */
if (g_strcmp0 (item_tag, "CHILDINFO") == 0) {
item_value = imapx_list_response_parse_childinfo (
stream, response, cancellable, error);
success = (item_value != NULL);
/* RFC 5465 "NOTIFY" */
} else if (g_strcmp0 (item_tag, "OLDNAME") == 0) {
item_value = imapx_list_response_parse_oldname (
stream, response, cancellable, error);
success = (item_value != NULL);
} else {
success = camel_imapx_stream_skip_until (
stream, ")", cancellable, error);
}
if (item_value != NULL) {
/* Takes ownership of the item_tag string. */
g_hash_table_insert (
response->priv->extended_items,
item_tag, g_variant_ref_sink (item_value));
} else {
g_free (item_tag);
}
return success;
}
/**
* camel_imapx_list_response_new:
* @stream: a #CamelIMAPXStream
* @cancellable: a #GCancellable
* @error: return location for a #GError, or %NULL
*
* Attempts to parse an IMAP LIST response from @stream and, if successful,
* stores the response data in a new #CamelIMAPXListResponse. If an error
* occurs, the function sets @error and returns %NULL.
*
* Returns: a #CamelIMAPXListResponse, or %NULL
*
* Since: 3.10
**/
CamelIMAPXListResponse *
camel_imapx_list_response_new (CamelIMAPXStream *stream,
GCancellable *cancellable,
GError **error)
{
CamelIMAPXListResponse *response;
camel_imapx_token_t tok;
guchar *token;
guint len;
const gchar *attribute;
g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (stream), NULL);
response = g_object_new (CAMEL_TYPE_IMAPX_LIST_RESPONSE, NULL);
/* Parse attributes. */
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != '(') {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list: expecting '('");
goto fail;
}
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
while (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN) {
camel_imapx_list_response_add_attribute (
response, (gchar *) token);
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
}
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != ')') {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list: expecting ')'");
goto fail;
}
/* Add implied attributes (see RFC 5258 section 3.4). */
/* "\NoInferiors" implies "\HasNoChildren" */
attribute = CAMEL_IMAPX_LIST_ATTR_NOINFERIORS;
if (camel_imapx_list_response_has_attribute (response, attribute)) {
attribute = CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN;
camel_imapx_list_response_add_attribute (response, attribute);
}
/* "\NonExistent" implies "\NoSelect" */
attribute = CAMEL_IMAPX_LIST_ATTR_NONEXISTENT;
if (camel_imapx_list_response_has_attribute (response, attribute)) {
attribute = CAMEL_IMAPX_LIST_ATTR_NOSELECT;
camel_imapx_list_response_add_attribute (response, attribute);
}
/* Parse separator. */
if (!camel_imapx_stream_nstring (stream, &token, cancellable, error))
goto fail;
if (token != NULL)
response->priv->separator = *token;
else
response->priv->separator = '\0';
/* Parse mailbox name. */
response->priv->mailbox_name = camel_imapx_parse_mailbox (
stream, response->priv->separator, cancellable, error);
if (response->priv->mailbox_name == NULL)
goto fail;
/* Parse extended info (optional). */
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok == '(') {
gboolean success;
extended_item_repeat:
success = imapx_list_response_parse_extended_item (
stream, response, cancellable, error);
if (!success)
goto fail;
tok = camel_imapx_stream_token (
stream, &token, &len, cancellable, error);
if (tok == IMAPX_TOK_ERROR)
goto fail;
if (tok != ')') {
camel_imapx_stream_ungettoken (
stream, tok, token, len);
goto extended_item_repeat;
}
} else if (tok == '\n') {
camel_imapx_stream_ungettoken (stream, tok, token, len);
} else {
g_set_error (
error, CAMEL_IMAPX_ERROR, 1,
"list: expecting '(' or NEWLINE");
goto fail;
}
return response;
fail:
g_clear_object (&response);
return NULL;
}
/**
* camel_imapx_list_response_hash:
* @response: a #CamelIMAPXListResponse
*
* Generates a hash value for @response based on the mailbox name. This
* function is intended for easily hashing a #CamelIMAPXListResponse to
* add to a #GHashTable or similar data structure.
*
* Returns: a hash value for @response
*
* Since: 3.10
**/
guint
camel_imapx_list_response_hash (CamelIMAPXListResponse *response)
{
const gchar *mailbox_name;
mailbox_name = camel_imapx_list_response_get_mailbox_name (response);
return g_str_hash (mailbox_name);
}
/**
* camel_imapx_list_response_equal:
* @response_a: the first #CamelIMAPXListResponse
* @response_b: the second #CamelIMAPXListResponse
*
* Checks two #CamelIMAPXListResponse instances for equality based on
* their mailbox names.
*
* Returns: %TRUE if @response_a and @response_b are equal
*
* Since: 3.10
**/
gboolean
camel_imapx_list_response_equal (CamelIMAPXListResponse *response_a,
CamelIMAPXListResponse *response_b)
{
const gchar *mailbox_a;
const gchar *mailbox_b;
mailbox_a = camel_imapx_list_response_get_mailbox_name (response_a);
mailbox_b = camel_imapx_list_response_get_mailbox_name (response_b);
return g_str_equal (mailbox_a, mailbox_b);
}
/**
* camel_imapx_list_response_compare:
* @response_a: the first #CamelIMAPXListResponse
* @response_b: the second #CamelIMAPXListResponse
*
* Compares two #CamelIMAPXListResponse instances by their mailbox names.
*
* Returns: a negative value if @response_a compares before @response_b,
* zero if they compare equal, or a positive value if @response_a
* compares after @response_b
*
* Since: 3.10
**/
gint
camel_imapx_list_response_compare (CamelIMAPXListResponse *response_a,
CamelIMAPXListResponse *response_b)
{
const gchar *mailbox_a;
const gchar *mailbox_b;
mailbox_a = camel_imapx_list_response_get_mailbox_name (response_a);
mailbox_b = camel_imapx_list_response_get_mailbox_name (response_b);
return g_strcmp0 (mailbox_a, mailbox_b);
}
/**
* camel_imapx_list_response_get_mailbox_name:
* @response: a #CamelIMAPXListResponse
*
* Returns the mailbox name for @response.
*
* Returns: the mailbox name
*
* Since: 3.10
**/
const gchar *
camel_imapx_list_response_get_mailbox_name (CamelIMAPXListResponse *response)
{
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
return response->priv->mailbox_name;
}
/**
* camel_imapx_list_response_get_separator:
* @response: a #CamelIMAPXListResponse
*
* Returns the mailbox path separator character for @response.
*
* Returns: the mailbox path separator character
*
* Since: 3.10
**/
gchar
camel_imapx_list_response_get_separator (CamelIMAPXListResponse *response)
{
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), '\0');
return response->priv->separator;
}
/* Flag macros appear here in the reference manual,
* so also documenting them here in the source code
* since the documentation is so repetitive. */
/**
* CAMEL_IMAPX_LIST_ATTR_NOINFERIORS:
*
* Refer to
* RFC 3501 section 7.2.2.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_NOSELECT:
*
* Refer to
* RFC 3501 section 7.2.2.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_MARKED:
*
* Refer to
* RFC 3501 section 7.2.2.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_UNMARKED:
*
* Refer to
* RFC 3501 section 7.2.2.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_NONEXISTENT:
*
* Refer to
* RFC 5258 section 3.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_SUBSCRIBED:
*
* Refer to
* RFC 5258 section 3.1.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_REMOTE:
*
* Refer to
* RFC 5258 section 3.1.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_HASCHILDREN:
*
* Refer to
* RFC 5258 section 4.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_HASNOCHILDREN:
*
* Refer to
* RFC 5258 section 4.
*
* Since: 3.10
**/
/**
* CAMEL_IMAPX_LIST_ATTR_ALL:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_ARCHIVE:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_DRAFTS:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_FLAGGED:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_JUNK:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_SENT:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* CAMEL_IMAPX_LIST_ATTR_TRASH:
*
* Refer to
* RFC 6154 section 2.
*
* Since: 3.12
**/
/**
* camel_imapx_list_response_add_attribute:
* @response: a #CamelIMAPXListResponse
* @attribute: a mailbox attribute
*
* Adds a mailbox attribute to @response. The @attribute should be one of
* the LIST attribute macros defined above.
*
* Since: 3.10
**/
void
camel_imapx_list_response_add_attribute (CamelIMAPXListResponse *response,
const gchar *attribute)
{
const gchar *canonical = NULL;
gint ii;
g_return_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response));
g_return_if_fail (attribute != NULL);
/* Try normalizing the attribute to match one of our
* pre-defined macros. */
for (ii = 0; ii < G_N_ELEMENTS (known_attributes); ii++) {
if (g_ascii_strcasecmp (attribute, known_attributes[ii]) == 0) {
canonical = known_attributes[ii];
break;
}
}
if (canonical == NULL)
canonical = g_intern_string (attribute);
g_hash_table_add (response->priv->attributes, (gpointer) canonical);
}
/**
* camel_imapx_list_response_has_attribute:
* @response: a #CamelIMAPXListResponse
* @attribute: a mailbox attribute
*
* Returns whether @response includes the given mailbox attribute. The
* @attribute should be one of the LIST attribute macros defined above.
*
* Returns: %TRUE if @response has @attribute, or else %FALSE
*
* Since: 3.10
**/
gboolean
camel_imapx_list_response_has_attribute (CamelIMAPXListResponse *response,
const gchar *attribute)
{
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), FALSE);
g_return_val_if_fail (attribute != NULL, FALSE);
return g_hash_table_contains (response->priv->attributes, attribute);
}
/**
* camel_imapx_list_response_dup_attributes:
* @response: a #CamelIMAPXListResponse
*
* Returns a #GHashTable of mailbox attributes to be used as a set.
* Use g_hash_table_contains() and g_hash_table_get_keys() to query
* for and list all mailbox attributes, respectively.
*
* The hash table keys are all internalized strings and must not be freed.
* Free the returned #GHashTable with g_hash_table_destroy() when finished
* with it.
*
* Returns: a newly-created #GHashTable
*
* Since: 3.10
**/
GHashTable *
camel_imapx_list_response_dup_attributes (CamelIMAPXListResponse *response)
{
GHashTable *hash_table;
GHashTableIter iter;
gpointer key;
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
hash_table = g_hash_table_new (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal);
g_hash_table_iter_init (&iter, response->priv->attributes);
while (g_hash_table_iter_next (&iter, &key, NULL))
g_hash_table_add (hash_table, key);
return hash_table;
}
/**
* camel_imapx_list_response_ref_extended_item:
* @response: a #CamelIMAPXListResponse
* @extended_item_tag: an extended item tag
*
* Returns the extended item value for @extended_item_tag as a #GVariant.
* The type of the #GVariant depends on the extended item. If no value
* for @extended_item_tag exists, the function returns %NULL.
*
* The returned #GVariant is referenced for thread-safety and should
* be unreferenced with g_variant_unref() when finished with it.
*
* Returns: a #GVariant, or %NULL
*
* Since: 3.10
**/
GVariant *
camel_imapx_list_response_ref_extended_item (CamelIMAPXListResponse *response,
const gchar *extended_item_tag)
{
GHashTable *extended_items;
GVariant *value;
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
g_return_val_if_fail (extended_item_tag != NULL, NULL);
extended_items = response->priv->extended_items;
value = g_hash_table_lookup (extended_items, extended_item_tag);
return (value != NULL) ? g_variant_ref (value) : NULL;
}
/**
* camel_imapx_list_response_get_oldname:
* @response: a #CamelIMAPXListResponse
*
* Convenience function returns the value of the "OLDNAME" extended data
* item, or %NULL if no such extended data item is present.
*
* The presence of this extended data item indicates the mailbox has been
* renamed. See
* RFC 5465 Section 5.4 for further details.
*
* Returns: the old mailbox name, or %NULL
*
* Since: 3.12
**/
const gchar *
camel_imapx_list_response_get_oldname (CamelIMAPXListResponse *response)
{
GHashTable *extended_items;
GVariant *value;
g_return_val_if_fail (CAMEL_IS_IMAPX_LIST_RESPONSE (response), NULL);
extended_items = response->priv->extended_items;
value = g_hash_table_lookup (extended_items, "OLDNAME");
return (value != NULL) ? g_variant_get_string (value, NULL) : NULL;
}