/* XMMS2 - X Music Multiplexer System * Copyright (C) 2003-2007 XMMS2 Team * * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. */ #include #include #include #include "xmms/xmms_log.h" #include "xmmspriv/xmms_xform.h" static GList *magic_list, *ext_list; #define SWAP16(v, endian) \ if (endian == G_LITTLE_ENDIAN) { \ v = GUINT16_TO_LE (v); \ } else if (endian == G_BIG_ENDIAN) { \ v = GUINT16_TO_BE (v); \ } #define SWAP32(v, endian) \ if (endian == G_LITTLE_ENDIAN) { \ v = GUINT32_TO_LE (v); \ } else if (endian == G_BIG_ENDIAN) { \ v = GUINT32_TO_BE (v); \ } #define CMP(v1, entry, v2) \ if (entry->pre_test_and_op) { \ v1 &= entry->pre_test_and_op; \ } \ \ switch (entry->oper) { \ case XMMS_MAGIC_ENTRY_OPERATOR_EQUAL: \ return v1 == v2; \ case XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN: \ return v1 < v2; \ case XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN: \ return v1 > v2; \ case XMMS_MAGIC_ENTRY_OPERATOR_AND: \ return (v1 & v2) == v2; \ case XMMS_MAGIC_ENTRY_OPERATOR_NAND: \ return (v1 & v2) != v2; \ } \ typedef enum xmms_magic_entry_type_St { XMMS_MAGIC_ENTRY_TYPE_UNKNOWN = 0, XMMS_MAGIC_ENTRY_TYPE_BYTE, XMMS_MAGIC_ENTRY_TYPE_INT16, XMMS_MAGIC_ENTRY_TYPE_INT32, XMMS_MAGIC_ENTRY_TYPE_STRING, XMMS_MAGIC_ENTRY_TYPE_STRINGC, } xmms_magic_entry_type_t; typedef enum xmms_magic_entry_operator_St { XMMS_MAGIC_ENTRY_OPERATOR_EQUAL = 0, XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN, XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN, XMMS_MAGIC_ENTRY_OPERATOR_AND, XMMS_MAGIC_ENTRY_OPERATOR_NAND } xmms_magic_entry_operator_t; typedef struct xmms_magic_entry_St { guint offset; xmms_magic_entry_type_t type; gint endian; guint len; guint pre_test_and_op; xmms_magic_entry_operator_t oper; union { guint8 i8; guint16 i16; guint32 i32; gchar s[32]; } value; } xmms_magic_entry_t; typedef struct xmms_magic_checker_St { xmms_xform_t *xform; gchar *buf; guint alloc; guint read; guint offset; } xmms_magic_checker_t; typedef struct xmms_magic_ext_data_St { gchar *type; gchar *pattern; } xmms_magic_ext_data_t; static void xmms_magic_tree_free (GNode *tree); static gchar *xmms_magic_match (xmms_magic_checker_t *c, const gchar *u); static guint xmms_magic_complexity (GNode *tree); static void xmms_magic_entry_free (xmms_magic_entry_t *e) { g_free (e); } static xmms_magic_entry_type_t parse_type (gchar **s, gint *endian) { struct { const gchar *string; xmms_magic_entry_type_t type; gint endian; } *t, types[] = { {"byte", XMMS_MAGIC_ENTRY_TYPE_BYTE, G_BYTE_ORDER}, {"short", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER}, {"long", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER}, {"beshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BIG_ENDIAN}, {"belong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_BIG_ENDIAN}, {"leshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_LITTLE_ENDIAN}, {"lelong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_LITTLE_ENDIAN}, {"string/c", XMMS_MAGIC_ENTRY_TYPE_STRINGC, G_BYTE_ORDER}, {"string", XMMS_MAGIC_ENTRY_TYPE_STRING, G_BYTE_ORDER}, {NULL, XMMS_MAGIC_ENTRY_TYPE_UNKNOWN, G_BYTE_ORDER} }; for (t = types; t; t++) { int l = t->string ? strlen (t->string) : 0; if (!l || !strncmp (*s, t->string, l)) { *s += l; *endian = t->endian; return t->type; } } g_assert_not_reached (); } static xmms_magic_entry_operator_t parse_oper (gchar **s) { gchar c = **s; struct { gchar c; xmms_magic_entry_operator_t o; } *o, opers[] = { {'=', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL}, {'<', XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN}, {'>', XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN}, {'&', XMMS_MAGIC_ENTRY_OPERATOR_AND}, {'^', XMMS_MAGIC_ENTRY_OPERATOR_NAND}, {'\0', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL} }; for (o = opers; o; o++) { if (!o->c) { /* no operator found */ return o->o; } else if (c == o->c) { (*s)++; /* skip operator */ return o->o; } } g_assert_not_reached (); } static gboolean parse_pre_test_and_op (xmms_magic_entry_t *entry, gchar **end) { gboolean ret = FALSE; if (**end == ' ') { (*end)++; return TRUE; } switch (entry->type) { case XMMS_MAGIC_ENTRY_TYPE_BYTE: case XMMS_MAGIC_ENTRY_TYPE_INT16: case XMMS_MAGIC_ENTRY_TYPE_INT32: if (**end == '&') { (*end)++; entry->pre_test_and_op = strtoul (*end, end, 0); ret = TRUE; } default: break; } return ret; } static xmms_magic_entry_t * parse_entry (const gchar *s) { xmms_magic_entry_t *entry; gchar *end = NULL; entry = g_new0 (xmms_magic_entry_t, 1); entry->endian = G_BYTE_ORDER; entry->oper = XMMS_MAGIC_ENTRY_OPERATOR_EQUAL; entry->offset = strtoul (s, &end, 0); end++; entry->type = parse_type (&end, &entry->endian); if (entry->type == XMMS_MAGIC_ENTRY_TYPE_UNKNOWN) { g_free (entry); return NULL; } if (!parse_pre_test_and_op (entry, &end)) { g_free (entry); return NULL; } /* @todo Implement string operators */ switch (entry->type) { case XMMS_MAGIC_ENTRY_TYPE_STRING: case XMMS_MAGIC_ENTRY_TYPE_STRINGC: break; default: entry->oper = parse_oper (&end); break; } switch (entry->type) { case XMMS_MAGIC_ENTRY_TYPE_BYTE: entry->value.i8 = strtoul (end, &end, 0); entry->len = 1; break; case XMMS_MAGIC_ENTRY_TYPE_INT16: entry->value.i16 = strtoul (end, &end, 0); entry->len = 2; break; case XMMS_MAGIC_ENTRY_TYPE_INT32: entry->value.i32 = strtoul (end, &end, 0); entry->len = 4; break; case XMMS_MAGIC_ENTRY_TYPE_STRING: case XMMS_MAGIC_ENTRY_TYPE_STRINGC: g_strlcpy (entry->value.s, end, sizeof (entry->value.s)); entry->len = strlen (entry->value.s); break; default: break; /* won't get here, handled above */ } return entry; } static gboolean free_node (GNode *node, xmms_magic_entry_t *entry) { if (G_NODE_IS_ROOT (node)) { gpointer *data = node->data; /* this isn't a magic entry, but the description of the tree */ g_free (data[0]); /* desc */ g_free (data[1]); /* mime */ g_free (data); } else { xmms_magic_entry_free (entry); } return FALSE; /* continue traversal */ } static void xmms_magic_tree_free (GNode *tree) { g_node_traverse (tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) free_node, NULL); } static GNode * xmms_magic_add_node (GNode *tree, const gchar *s, GNode *prev_node) { xmms_magic_entry_t *entry; gpointer *data = tree->data; guint indent = 0, prev_indent; g_assert (s); XMMS_DBG ("adding magic spec to tree '%s'", (gchar *) data[0]); /* indent level is number of leading '>' characters */ while (*s == '>') { indent++; s++; } entry = parse_entry (s); if (!entry) { XMMS_DBG ("cannot parse magic entry"); return NULL; } if (!indent) { return g_node_append_data (tree, entry); } if (!prev_node) { XMMS_DBG ("invalid indent level"); xmms_magic_entry_free (entry); return NULL; } prev_indent = g_node_depth (prev_node) - 2; if (indent > prev_indent) { /* larger jumps are invalid */ if (indent != prev_indent + 1) { XMMS_DBG ("invalid indent level"); xmms_magic_entry_free (entry); return NULL; } return g_node_append_data (prev_node, entry); } else { while (indent < prev_indent) { prev_indent--; prev_node = prev_node->parent; } return g_node_insert_after (prev_node->parent, prev_node, g_node_new (entry)); } } static gint read_data (xmms_magic_checker_t *c, guint needed) { xmms_error_t e; if (needed > c->alloc) { c->alloc = needed; c->buf = g_realloc (c->buf, c->alloc); } xmms_error_reset (&e); return xmms_xform_peek (c->xform, c->buf, needed, &e); } static gboolean node_match (xmms_magic_checker_t *c, GNode *node) { xmms_magic_entry_t *entry = node->data; guint needed = c->offset + entry->offset + entry->len; guint8 i8; guint16 i16; guint32 i32; gint tmp; gchar *ptr; /* do we have enough data ready for this check? * if not, read some more */ if (c->read < needed) { tmp = read_data (c, needed); if (tmp == -1) { return FALSE; } c->read = tmp; if (c->read < needed) { /* couldn't read enough data */ return FALSE; } } ptr = &c->buf[c->offset + entry->offset]; switch (entry->type) { case XMMS_MAGIC_ENTRY_TYPE_BYTE: memcpy (&i8, ptr, sizeof (i8)); CMP (i8, entry, entry->value.i8); /* returns */ case XMMS_MAGIC_ENTRY_TYPE_INT16: memcpy (&i16, ptr, sizeof (i16)); SWAP16 (i16, entry->endian); CMP (i16, entry, entry->value.i16); /* returns */ case XMMS_MAGIC_ENTRY_TYPE_INT32: memcpy (&i32, ptr, sizeof (i32)); SWAP32 (i32, entry->endian); CMP (i32, entry, entry->value.i32); /* returns */ case XMMS_MAGIC_ENTRY_TYPE_STRING: return !strncmp (ptr, entry->value.s, entry->len); case XMMS_MAGIC_ENTRY_TYPE_STRINGC: return !g_ascii_strncasecmp (ptr, entry->value.s, entry->len); default: return FALSE; } } static gboolean tree_match (xmms_magic_checker_t *c, GNode *tree) { GNode *n; /* empty subtrees match anything */ if (!tree->children) { return TRUE; } for (n = tree->children; n; n = n->next) { if (node_match (c, n) && tree_match (c, n)) { return TRUE; } } return FALSE; } guint tree_bytes_max_needed (xmms_magic_checker_t *c, GNode *tree) { GNode *n; guint ret = 0; for (n = tree->children; n; n = n->next) { xmms_magic_entry_t *entry = n->data; ret = MAX (ret, c->offset + entry->offset + entry->len); ret = MAX (ret, tree_bytes_max_needed (c, n)); } return ret; } static gchar * xmms_magic_match (xmms_magic_checker_t *c, const gchar *uri) { const GList *l; gchar *u; g_return_val_if_fail (c, NULL); /* only one of the contained sets has to match */ for (l = magic_list; l; l = g_list_next (l)) { GNode *tree = l->data; if (tree_match (c, tree)) { gpointer *data = tree->data; XMMS_DBG ("magic plugin detected '%s' (%s)", (char *)data[1], (char *)data[0]); return (char *) (data[1]); } } if (!uri) return NULL; u = g_ascii_strdown (uri, -1); for (l = ext_list; l; l = g_list_next (l)) { xmms_magic_ext_data_t *e = l->data; if (g_pattern_match_simple (e->pattern, u)) { XMMS_DBG ("magic plugin detected '%s' (by extension '%s')", e->type, e->pattern); g_free (u); return e->type; } } g_free (u); return NULL; } static guint xmms_magic_complexity (GNode *tree) { return g_node_n_nodes (tree, G_TRAVERSE_ALL); } static gint cb_sort_magic_list (GNode *a, GNode *b) { guint n1, n2; n1 = xmms_magic_complexity (a); n2 = xmms_magic_complexity (b); if (n1 > n2) { return -1; } else if (n1 < n2) { return 1; } else { return 0; } } gboolean xmms_magic_extension_add (const gchar *mime, const gchar *ext) { xmms_magic_ext_data_t *e; g_return_val_if_fail (mime, FALSE); g_return_val_if_fail (ext, FALSE); e = g_new0 (xmms_magic_ext_data_t, 1); e->pattern = g_strdup (ext); e->type = g_strdup (mime); ext_list = g_list_prepend (ext_list, e); return TRUE; } gboolean xmms_magic_add (const gchar *desc, const gchar *mime, ...) { GNode *tree, *node = NULL; va_list ap; gchar *s; gpointer *root_props; gboolean ret = TRUE; g_return_val_if_fail (desc, FALSE); g_return_val_if_fail (mime, FALSE); /* now process the magic specs in the argument list */ va_start (ap, mime); s = va_arg (ap, gchar *); if (!s) { /* no magic specs passed -> failure */ va_end (ap); return FALSE; } /* root node stores the description and the mimetype */ root_props = g_new0 (gpointer, 2); root_props[0] = g_strdup (desc); root_props[1] = g_strdup (mime); tree = g_node_new (root_props); do { if (!strlen (s)) { ret = FALSE; xmms_log_error ("invalid magic spec: '%s'", s); break; } s = g_strdup (s); /* we need our own copy */ node = xmms_magic_add_node (tree, s, node); g_free (s); if (!node) { xmms_log_error ("invalid magic spec: '%s'", s); ret = FALSE; break; } } while ((s = va_arg (ap, gchar *))); va_end (ap); /* only add this tree to the list if all spec chunks are valid */ if (ret) { magic_list = g_list_insert_sorted (magic_list, tree, (GCompareFunc) cb_sort_magic_list); } else { xmms_magic_tree_free (tree); } return ret; } static gboolean xmms_magic_plugin_init (xmms_xform_t *xform) { xmms_magic_checker_t c; gchar *res; const gchar *url; c.xform = xform; c.read = c.offset = 0; c.alloc = 128; /* start with a 128 bytes buffer */ c.buf = g_malloc (c.alloc); url = xmms_xform_indata_find_str (xform, XMMS_STREAM_TYPE_URL); res = xmms_magic_match (&c, url); if (res) { xmms_xform_metadata_set_str (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_MIME, res); xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, res, XMMS_STREAM_TYPE_END); } g_free (c.buf); return !!res; } static void xmms_magic_plugin_destroy (xmms_xform_t *xform) { } static gint xmms_magic_plugin_read (xmms_xform_t *xform, void *buffer, gint len, xmms_error_t *error) { return xmms_xform_read (xform, buffer, len, error); } static gint64 xmms_magic_plugin_seek (xmms_xform_t *xform, gint64 offset, xmms_xform_seek_mode_t whence, xmms_error_t *err) { return xmms_xform_seek (xform, offset, whence, err); } static gboolean xmms_magic_plugin_setup (xmms_xform_plugin_t *xform_plugin) { xmms_xform_methods_t methods; XMMS_XFORM_METHODS_INIT (methods); methods.init = xmms_magic_plugin_init; methods.destroy = xmms_magic_plugin_destroy; methods.read = xmms_magic_plugin_read; methods.seek = xmms_magic_plugin_seek; xmms_xform_plugin_methods_set (xform_plugin, &methods); xmms_xform_plugin_indata_add (xform_plugin, XMMS_STREAM_TYPE_MIMETYPE, "application/octet-stream", XMMS_STREAM_TYPE_END); return TRUE; } XMMS_XFORM_BUILTIN (magic, "Magic file identifier", XMMS_VERSION, "Magic file identifier", xmms_magic_plugin_setup);