/* * Copyright (C) 2004-2005 Nokia Corporation. * Copyright (C) 2008 Bastien Nocera * * This program 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; version 2 of the * License. * * This program 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 program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "gvfsbackendobexftp-cap-parser.h" #define d(x) struct _OvuCaps { GList *memory_entries; /* FIXME: Add "Services" and "Inbox" data here later. */ }; struct _OvuCapsMemory { gchar *type; goffset free; goffset used; guint has_free : 1; guint has_used : 1; guint case_sensitive : 1; }; typedef enum { PARSER_STATE_INVALID, PARSER_STATE_START, PARSER_STATE_CAPABILITY, PARSER_STATE_GENERAL, PARSER_STATE_MEMORY, PARSER_STATE_MEMORY_TYPE, PARSER_STATE_MEMORY_LOCATION, PARSER_STATE_MEMORY_FREE, PARSER_STATE_MEMORY_USED, PARSER_STATE_MEMORY_SHARED, PARSER_STATE_MEMORY_FILESIZE, PARSER_STATE_MEMORY_FOLDERSIZE, PARSER_STATE_MEMORY_FILELEN, PARSER_STATE_MEMORY_FOLDERLEN, PARSER_STATE_MEMORY_CASE, PARSER_STATE_MEMORY_EXT, PARSER_STATE_INBOX, PARSER_STATE_SERVICE, PARSER_STATE_SKIP } ParserState; typedef struct { GList *state; GList *memory_entries; gchar *memory_type; goffset memory_free; goffset memory_used; gboolean memory_has_free; gboolean memory_has_used; gboolean memory_case_sensitive; GError **error; } ParserData; static void cap_parser_start_node_cb (void *user_data, const char *node_name, const char **attr); static void cap_parser_end_node_cb (void *user_data, const char *node_name); static void cap_parser_text_cb (void *user_data, const XML_Char *s, int len); static XML_Parser cap_parser_create_parser (ParserData *data); static void cap_parser_push_state (ParserData *data, ParserState state) { data->state = g_list_prepend (data->state, GINT_TO_POINTER (state)); } static ParserState cap_parser_pop_state (ParserData *data) { ParserState state; if (!data->state) { return PARSER_STATE_INVALID; } state = GPOINTER_TO_INT (data->state->data); data->state = g_list_delete_link (data->state, data->state); return state; } static ParserState cap_parser_peek_state (ParserData *data) { if (!data->state) { return PARSER_STATE_START; } return GPOINTER_TO_INT (data->state->data); } static const char * cap_parser_get_attribute_value (const char *name, const char **attr) { gint i = 0; while (attr[i]) { if (strcmp (name, attr[i]) == 0) { return attr[i + 1]; } i += 2; } return ""; } static void cap_parser_start_node_cb (void *user_data, const char *node_name, const char **attr) { ParserData *data; ParserState state; const gchar *version; data = (ParserData *) user_data; state = cap_parser_peek_state (data); switch (state) { case PARSER_STATE_START: if (strcmp (node_name, "Capability") != 0) { g_set_error (data->error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Outermost element must be a , not <%s>", node_name); return; } version = cap_parser_get_attribute_value ("version", attr); /* Assume an empty version is fine */ if (strcmp (version, "1.0") != 0 && version[0] != '\0') { g_warning ("Version expected is '1.0', not '%s'\n", version); } cap_parser_push_state (data, PARSER_STATE_CAPABILITY); break; case PARSER_STATE_CAPABILITY: if (strcmp (node_name, "General") == 0) { cap_parser_push_state (data, PARSER_STATE_GENERAL); } else if (strcmp (node_name, "Inbox") == 0) { cap_parser_push_state (data, PARSER_STATE_INBOX); } else if (strcmp (node_name, "Service") == 0) { cap_parser_push_state (data, PARSER_STATE_SERVICE); } else { g_set_error (data->error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Don't expect node '%s' as child of 'Cap'", node_name); return; } break; case PARSER_STATE_GENERAL: if (strcmp (node_name, "Memory") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY); } else if (strcmp (node_name, "Manufacturer") == 0 || strcmp (node_name, "Model") == 0 || strcmp (node_name, "SN") == 0 || strcmp (node_name, "OEM") == 0 || strcmp (node_name, "SW") == 0 || strcmp (node_name, "FW") == 0 || strcmp (node_name, "HW") == 0 || strcmp (node_name, "Language") == 0 || strcmp (node_name, "Ext") == 0) { /* Skip these for now. */ cap_parser_push_state (data, PARSER_STATE_SKIP); } else { g_set_error (data->error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Don't expect node '%s' as child of 'General'", node_name); return; } break; case PARSER_STATE_MEMORY: if (strcmp (node_name, "MemType") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_TYPE); } else if (strcmp (node_name, "Location") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_LOCATION); } else if (strcmp (node_name, "Free") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_FREE); } else if (strcmp (node_name, "Used") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_USED); } else if (strcmp (node_name, "Shared") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_SHARED); } else if (strcmp (node_name, "FileSize") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_FILESIZE); } else if (strcmp (node_name, "FolderSize") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_FOLDERSIZE); } else if (strcmp (node_name, "FileNLen") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_FILELEN); } else if (strcmp (node_name, "FolderNLen") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_FOLDERLEN); } else if (strcmp (node_name, "CaseSenN") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_CASE); data->memory_case_sensitive = TRUE; } else if (strcmp (node_name, "Ext") == 0) { cap_parser_push_state (data, PARSER_STATE_MEMORY_EXT); } else { g_set_error (data->error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Don't expect node '%s' as child of 'Memory'", node_name); return; } break; case PARSER_STATE_INBOX: case PARSER_STATE_SERVICE: /* Skip these for now. */ cap_parser_push_state (data, PARSER_STATE_SKIP); break; case PARSER_STATE_SKIP: cap_parser_push_state (data, PARSER_STATE_SKIP); break; default: g_warning ("Node not handled: '%s'\n", node_name); cap_parser_push_state (data, PARSER_STATE_SKIP); break; } } static void cap_parser_reset_memory (ParserData *data) { g_free (data->memory_type); data->memory_type = NULL; data->memory_free = 0; data->memory_used = 0; data->memory_has_free = FALSE; data->memory_has_used = FALSE; data->memory_case_sensitive = FALSE; } static void cap_parser_end_node_cb (void *user_data, const char *node_name) { ParserData *data; ParserState state; OvuCapsMemory *memory; data = (ParserData *) user_data; state = cap_parser_pop_state (data); switch (state) { case PARSER_STATE_INVALID: return; case PARSER_STATE_MEMORY: memory = ovu_caps_memory_new (data->memory_type, data->memory_free, data->memory_used, data->memory_has_free, data->memory_has_used, data->memory_case_sensitive); data->memory_entries = g_list_prepend (data->memory_entries, memory); cap_parser_reset_memory (data); break; case PARSER_STATE_CAPABILITY: data->memory_entries = g_list_reverse (data->memory_entries); break; default: break; } } /* Parse a long, return -1 if input is not strictly valid or null. */ static goffset parse_long (const gchar *str, gboolean *success) { gchar *endptr; glong l; *success = TRUE; if (!str) { *success = FALSE; return 0; } l = strtol (str, &endptr, 10); if (endptr[0] != '\0' || l < 0) { *success = FALSE; l = 0; } return l; } static void cap_parser_text_cb (void *user_data, const XML_Char *s, int len) { ParserData *data; ParserState state; gchar *tmp; data = (ParserData *) user_data; /* text is not null terminated. */ tmp = g_strndup (s, len); state = cap_parser_peek_state (data); switch (state) { case PARSER_STATE_MEMORY_TYPE: data->memory_type = g_strdup (tmp); break; case PARSER_STATE_MEMORY_FREE: data->memory_free = parse_long (tmp, &data->memory_has_free); break; case PARSER_STATE_MEMORY_USED: data->memory_used = parse_long (tmp, &data->memory_has_used); break; default: break; } g_free (tmp); } static XML_Parser cap_parser_create_parser (ParserData *data) { XML_Parser parser; parser = XML_ParserCreate (NULL); XML_SetElementHandler (parser, cap_parser_start_node_cb, cap_parser_end_node_cb); XML_SetCharacterDataHandler (parser, cap_parser_text_cb); XML_SetUserData (parser, data); return parser; } static void cap_parser_free (ParserData *data, gboolean free_data) { cap_parser_reset_memory (data); if (free_data) { g_list_foreach (data->memory_entries, (GFunc) ovu_caps_memory_free, NULL); } g_free (data); } OvuCaps * ovu_caps_parser_parse (const gchar *buf, gint len, GError **error) { ParserData *data; XML_Parser parser; OvuCaps *caps; data = g_new0 (ParserData, 1); data->error = error; parser = cap_parser_create_parser (data); if (XML_Parse (parser, buf, len, TRUE) == 0) { caps = NULL; if (*error == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Couldn't parse the incoming data"); } cap_parser_free (data, TRUE); } else { caps = g_new0 (OvuCaps, 1); caps->memory_entries = data->memory_entries; cap_parser_free (data, FALSE); } XML_ParserFree (parser); return caps; } OvuCapsMemory * ovu_caps_memory_new (const gchar *type, goffset free, goffset used, gboolean has_free, gboolean has_used, gboolean case_sensitive) { OvuCapsMemory *memory; memory = g_new0 (OvuCapsMemory, 1); memory->type = g_strdup (type); memory->free = free; memory->used = used; memory->has_free = has_free; memory->has_used = has_used; memory->case_sensitive = case_sensitive; return memory; } void ovu_caps_memory_free (OvuCapsMemory *memory) { g_free (memory->type); g_free (memory); } gboolean ovu_caps_memory_equal (OvuCapsMemory *m1, OvuCapsMemory *m2) { if (strcmp (m1->type, m2->type) != 0) { d(g_print ("type mismatch: %s %s\n", m1->type, m2->type)); return FALSE; } if (m1->free != m2->free) { d(g_print ("free mismatch: %d %d\n", (int) m1->free, (int) m2->free)); return FALSE; } if (m1->used != m2->used) { d(g_print ("used mismatch: %d %d\n", (int) m1->used, (int) m2->used)); return FALSE; } if (m1->case_sensitive != m2->case_sensitive) { d(g_print ("case mismatch: %d %d\n", m1->case_sensitive, m2->case_sensitive)); return FALSE; } return TRUE; } void ovu_caps_free (OvuCaps *caps) { g_list_foreach (caps->memory_entries, (GFunc) ovu_caps_memory_free, NULL); g_list_free (caps->memory_entries); g_free (caps); } GList * ovu_caps_get_memory_entries (OvuCaps *caps) { g_return_val_if_fail (caps != NULL, NULL); return caps->memory_entries; } OvuCapsMemory * ovu_caps_get_memory_type (OvuCaps *caps, const gchar *mem_type) { GList *tmp; g_return_val_if_fail (caps != NULL, NULL); for (tmp = caps->memory_entries; tmp != NULL; tmp = tmp->next) { OvuCapsMemory *memory = tmp->data; /* treat a NULL memory type as matching anything */ if (mem_type == NULL || (memory->type != NULL && !strcmp(mem_type, memory->type))) return memory; } return NULL; } const gchar * ovu_caps_memory_get_type (OvuCapsMemory *memory) { return memory->type; } goffset ovu_caps_memory_get_used (OvuCapsMemory *memory) { return memory->used; } goffset ovu_caps_memory_get_free (OvuCapsMemory *memory) { return memory->free; } gboolean ovu_caps_memory_has_used (OvuCapsMemory *memory) { return memory->has_used; } gboolean ovu_caps_memory_has_free (OvuCapsMemory *memory) { return memory->has_free; } gboolean ovu_caps_memory_get_case_sensitive (OvuCapsMemory *memory) { return memory->case_sensitive; }