XMMS2
src/xmms/magic.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 
00018 #include <glib.h>
00019 #include <glib/gprintf.h>
00020 #include <string.h>
00021 #include <stdlib.h>
00022 
00023 #include "xmms/xmms_log.h"
00024 #include "xmmspriv/xmms_xform.h"
00025 
00026 static GList *magic_list, *ext_list;
00027 
00028 #define SWAP16(v, endian) \
00029     if (endian == G_LITTLE_ENDIAN) { \
00030         v = GUINT16_TO_LE (v); \
00031     } else if (endian == G_BIG_ENDIAN) { \
00032         v = GUINT16_TO_BE (v); \
00033     }
00034 
00035 #define SWAP32(v, endian) \
00036     if (endian == G_LITTLE_ENDIAN) { \
00037         v = GUINT32_TO_LE (v); \
00038     } else if (endian == G_BIG_ENDIAN) { \
00039         v = GUINT32_TO_BE (v); \
00040     }
00041 
00042 #define CMP(v1, entry, v2) \
00043     if (entry->pre_test_and_op) { \
00044         v1 &= entry->pre_test_and_op; \
00045     } \
00046 \
00047     switch (entry->oper) { \
00048         case XMMS_MAGIC_ENTRY_OPERATOR_EQUAL: \
00049             return v1 == v2; \
00050         case XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN: \
00051             return v1 < v2; \
00052         case XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN: \
00053             return v1 > v2; \
00054         case XMMS_MAGIC_ENTRY_OPERATOR_AND: \
00055             return (v1 & v2) == v2; \
00056         case XMMS_MAGIC_ENTRY_OPERATOR_NAND: \
00057             return (v1 & v2) != v2; \
00058     } \
00059 
00060 typedef enum xmms_magic_entry_type_St {
00061     XMMS_MAGIC_ENTRY_TYPE_UNKNOWN = 0,
00062     XMMS_MAGIC_ENTRY_TYPE_BYTE,
00063     XMMS_MAGIC_ENTRY_TYPE_INT16,
00064     XMMS_MAGIC_ENTRY_TYPE_INT32,
00065     XMMS_MAGIC_ENTRY_TYPE_STRING,
00066     XMMS_MAGIC_ENTRY_TYPE_STRINGC,
00067 } xmms_magic_entry_type_t;
00068 
00069 typedef enum xmms_magic_entry_operator_St {
00070     XMMS_MAGIC_ENTRY_OPERATOR_EQUAL = 0,
00071     XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN,
00072     XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN,
00073     XMMS_MAGIC_ENTRY_OPERATOR_AND,
00074     XMMS_MAGIC_ENTRY_OPERATOR_NAND
00075 } xmms_magic_entry_operator_t;
00076 
00077 typedef struct xmms_magic_entry_St {
00078     guint offset;
00079     xmms_magic_entry_type_t type;
00080     gint endian;
00081     guint len;
00082     guint pre_test_and_op;
00083     xmms_magic_entry_operator_t oper;
00084 
00085     union {
00086         guint8 i8;
00087         guint16 i16;
00088         guint32 i32;
00089         gchar s[32];
00090     } value;
00091 } xmms_magic_entry_t;
00092 
00093 typedef struct xmms_magic_checker_St {
00094     xmms_xform_t *xform;
00095     gchar *buf;
00096     guint alloc;
00097     guint read;
00098     guint offset;
00099     gint dumpcount;
00100 } xmms_magic_checker_t;
00101 
00102 typedef struct xmms_magic_ext_data_St {
00103     gchar *type;
00104     gchar *pattern;
00105 } xmms_magic_ext_data_t;
00106 
00107 static void xmms_magic_tree_free (GNode *tree);
00108 
00109 static gchar *xmms_magic_match (xmms_magic_checker_t *c, const gchar *u);
00110 static guint xmms_magic_complexity (GNode *tree);
00111 
00112 static void
00113 xmms_magic_entry_free (xmms_magic_entry_t *e)
00114 {
00115     g_free (e);
00116 }
00117 
00118 static xmms_magic_entry_type_t
00119 parse_type (gchar **s, gint *endian)
00120 {
00121     struct {
00122         const gchar *string;
00123         xmms_magic_entry_type_t type;
00124         gint endian;
00125     } *t, types[] = {
00126         {"byte", XMMS_MAGIC_ENTRY_TYPE_BYTE, G_BYTE_ORDER},
00127         {"short", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
00128         {"long", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
00129         {"beshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BIG_ENDIAN},
00130         {"belong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_BIG_ENDIAN},
00131         {"leshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_LITTLE_ENDIAN},
00132         {"lelong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_LITTLE_ENDIAN},
00133         {"string/c", XMMS_MAGIC_ENTRY_TYPE_STRINGC, G_BYTE_ORDER},
00134         {"string", XMMS_MAGIC_ENTRY_TYPE_STRING, G_BYTE_ORDER},
00135         {NULL, XMMS_MAGIC_ENTRY_TYPE_UNKNOWN, G_BYTE_ORDER}
00136     };
00137 
00138     for (t = types; t; t++) {
00139         int l = t->string ? strlen (t->string) : 0;
00140 
00141         if (!l || !strncmp (*s, t->string, l)) {
00142             *s += l;
00143             *endian = t->endian;
00144 
00145             return t->type;
00146         }
00147     }
00148 
00149     g_assert_not_reached ();
00150 }
00151 
00152 
00153 static xmms_magic_entry_operator_t
00154 parse_oper (gchar **s)
00155 {
00156     gchar c = **s;
00157     struct {
00158         gchar c;
00159         xmms_magic_entry_operator_t o;
00160     } *o, opers[] = {
00161         {'=', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL},
00162         {'<', XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN},
00163         {'>', XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN},
00164         {'&', XMMS_MAGIC_ENTRY_OPERATOR_AND},
00165         {'^', XMMS_MAGIC_ENTRY_OPERATOR_NAND},
00166         {'\0', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL}
00167     };
00168 
00169     for (o = opers; o; o++) {
00170         if (!o->c) {
00171             /* no operator found */
00172             return o->o;
00173         } else if (c == o->c) {
00174             (*s)++; /* skip operator */
00175             return o->o;
00176         }
00177     }
00178 
00179     g_assert_not_reached ();
00180 }
00181 
00182 static gboolean
00183 parse_pre_test_and_op (xmms_magic_entry_t *entry, gchar **end)
00184 {
00185     gboolean ret = FALSE;
00186 
00187     if (**end == ' ') {
00188         (*end)++;
00189         return TRUE;
00190     }
00191 
00192     switch (entry->type) {
00193         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00194         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00195         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00196             if (**end == '&') {
00197                 (*end)++;
00198                 entry->pre_test_and_op = strtoul (*end, end, 0);
00199                 ret = TRUE;
00200             }
00201         default:
00202             break;
00203     }
00204 
00205     return ret;
00206 }
00207 
00208 static xmms_magic_entry_t *
00209 parse_entry (const gchar *s)
00210 {
00211     xmms_magic_entry_t *entry;
00212     gchar *end = NULL;
00213 
00214     entry = g_new0 (xmms_magic_entry_t, 1);
00215     entry->endian = G_BYTE_ORDER;
00216     entry->oper = XMMS_MAGIC_ENTRY_OPERATOR_EQUAL;
00217     entry->offset = strtoul (s, &end, 0);
00218 
00219     end++;
00220 
00221     entry->type = parse_type (&end, &entry->endian);
00222     if (entry->type == XMMS_MAGIC_ENTRY_TYPE_UNKNOWN) {
00223         g_free (entry);
00224         return NULL;
00225     }
00226 
00227     if (!parse_pre_test_and_op (entry, &end)) {
00228         g_free (entry);
00229         return NULL;
00230     }
00231 
00232     /* @todo Implement string operators */
00233     switch (entry->type) {
00234         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00235         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00236             break;
00237         default:
00238             entry->oper = parse_oper (&end);
00239             break;
00240     }
00241 
00242     switch (entry->type) {
00243         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00244             entry->value.i8 = strtoul (end, &end, 0);
00245             entry->len = 1;
00246             break;
00247         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00248             entry->value.i16 = strtoul (end, &end, 0);
00249             entry->len = 2;
00250             break;
00251         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00252             entry->value.i32 = strtoul (end, &end, 0);
00253             entry->len = 4;
00254             break;
00255         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00256         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00257             g_strlcpy (entry->value.s, end, sizeof (entry->value.s));
00258             entry->len = strlen (entry->value.s);
00259             break;
00260         default:
00261             break; /* won't get here, handled above */
00262     }
00263 
00264     return entry;
00265 }
00266 
00267 static gboolean
00268 free_node (GNode *node, xmms_magic_entry_t *entry)
00269 {
00270     if (G_NODE_IS_ROOT (node)) {
00271         gpointer *data = node->data;
00272 
00273         /* this isn't a magic entry, but the description of the tree */
00274         g_free (data[0]); /* desc */
00275         g_free (data[1]); /* mime */
00276         g_free (data);
00277     } else {
00278         xmms_magic_entry_free (entry);
00279     }
00280 
00281     return FALSE; /* continue traversal */
00282 }
00283 
00284 static void
00285 xmms_magic_tree_free (GNode *tree)
00286 {
00287     g_node_traverse (tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
00288                      (GNodeTraverseFunc) free_node, NULL);
00289 }
00290 
00291 static GNode *
00292 xmms_magic_add_node (GNode *tree, const gchar *s, GNode *prev_node)
00293 {
00294     xmms_magic_entry_t *entry;
00295     gpointer *data = tree->data;
00296     guint indent = 0, prev_indent;
00297 
00298     g_assert (s);
00299 
00300     XMMS_DBG ("adding magic spec to tree '%s'", (gchar *) data[0]);
00301 
00302     /* indent level is number of leading '>' characters */
00303     while (*s == '>') {
00304         indent++;
00305         s++;
00306     }
00307 
00308     entry = parse_entry (s);
00309     if (!entry) {
00310         XMMS_DBG ("cannot parse magic entry");
00311         return NULL;
00312     }
00313 
00314     if (!indent) {
00315         return g_node_append_data (tree, entry);
00316     }
00317 
00318     if (!prev_node) {
00319         XMMS_DBG ("invalid indent level");
00320         xmms_magic_entry_free (entry);
00321         return NULL;
00322     }
00323 
00324     prev_indent = g_node_depth (prev_node) - 2;
00325 
00326     if (indent > prev_indent) {
00327         /* larger jumps are invalid */
00328         if (indent != prev_indent + 1) {
00329             XMMS_DBG ("invalid indent level");
00330             xmms_magic_entry_free (entry);
00331             return NULL;
00332         }
00333 
00334         return g_node_append_data (prev_node, entry);
00335     } else {
00336         while (indent < prev_indent) {
00337             prev_indent--;
00338             prev_node = prev_node->parent;
00339         }
00340 
00341         return g_node_insert_after (prev_node->parent, prev_node,
00342                                     g_node_new (entry));
00343     }
00344 }
00345 
00346 static gint
00347 read_data (xmms_magic_checker_t *c, guint needed)
00348 {
00349     xmms_error_t e;
00350 
00351     if (needed > c->alloc) {
00352         c->alloc = needed;
00353         c->buf = g_realloc (c->buf, c->alloc);
00354     }
00355 
00356     xmms_error_reset (&e);
00357 
00358     return xmms_xform_peek (c->xform, c->buf, needed, &e);
00359 }
00360 
00361 static gboolean
00362 node_match (xmms_magic_checker_t *c, GNode *node)
00363 {
00364     xmms_magic_entry_t *entry = node->data;
00365     guint needed = c->offset + entry->offset + entry->len;
00366     guint8 i8;
00367     guint16 i16;
00368     guint32 i32;
00369     gint tmp;
00370     gchar *ptr;
00371 
00372     /* do we have enough data ready for this check?
00373      * if not, read some more
00374      */
00375     if (c->read < needed) {
00376         tmp = read_data (c, needed);
00377         if (tmp == -1) {
00378             return FALSE;
00379         }
00380 
00381         c->read = tmp;
00382         if (c->read < needed) {
00383             /* couldn't read enough data */
00384             return FALSE;
00385         }
00386     }
00387 
00388     ptr = &c->buf[c->offset + entry->offset];
00389 
00390     switch (entry->type) {
00391         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00392             memcpy (&i8, ptr, sizeof (i8));
00393             CMP (i8, entry, entry->value.i8); /* returns */
00394         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00395             memcpy (&i16, ptr, sizeof (i16));
00396             SWAP16 (i16, entry->endian);
00397             CMP (i16, entry, entry->value.i16); /* returns */
00398         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00399             memcpy (&i32, ptr, sizeof (i32));
00400             SWAP32 (i32, entry->endian);
00401             CMP (i32, entry, entry->value.i32); /* returns */
00402         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00403             return !strncmp (ptr, entry->value.s, entry->len);
00404         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00405             return !g_ascii_strncasecmp (ptr, entry->value.s, entry->len);
00406         default:
00407             return FALSE;
00408     }
00409 }
00410 
00411 static gboolean
00412 tree_match (xmms_magic_checker_t *c, GNode *tree)
00413 {
00414     GNode *n;
00415 
00416     /* empty subtrees match anything */
00417     if (!tree->children) {
00418         return TRUE;
00419     }
00420 
00421     for (n = tree->children; n; n = n->next) {
00422         if (node_match (c, n) && tree_match (c, n)) {
00423             return TRUE;
00424         }
00425     }
00426 
00427     return FALSE;
00428 }
00429 
00430 static gchar *
00431 xmms_magic_match (xmms_magic_checker_t *c, const gchar *uri)
00432 {
00433     const GList *l;
00434     gchar *u, *dump;
00435     int i;
00436 
00437     g_return_val_if_fail (c, NULL);
00438 
00439     /* only one of the contained sets has to match */
00440     for (l = magic_list; l; l = g_list_next (l)) {
00441         GNode *tree = l->data;
00442 
00443         if (tree_match (c, tree)) {
00444             gpointer *data = tree->data;
00445             XMMS_DBG ("magic plugin detected '%s' (%s)",
00446                       (char *)data[1], (char *)data[0]);
00447             return (char *) (data[1]);
00448         }
00449     }
00450 
00451     if (!uri)
00452         return NULL;
00453 
00454     u = g_ascii_strdown (uri, -1);
00455     for (l = ext_list; l; l = g_list_next (l)) {
00456         xmms_magic_ext_data_t *e = l->data;
00457         if (g_pattern_match_simple (e->pattern, u)) {
00458             XMMS_DBG ("magic plugin detected '%s' (by extension '%s')", e->type, e->pattern);
00459             g_free (u);
00460             return e->type;
00461         }
00462     }
00463     g_free (u);
00464 
00465     if (c->dumpcount > 0) {
00466         dump = g_malloc ((MIN (c->read, c->dumpcount) * 3) + 1);
00467         u = dump;
00468 
00469         XMMS_DBG ("Magic didn't match anything...");
00470         for (i = 0; i < c->dumpcount && i < c->read; i++) {
00471             g_sprintf (u, "%02X ", (unsigned char)c->buf[i]);
00472             u += 3;
00473         }
00474         XMMS_DBG ("%s", dump);
00475 
00476         g_free (dump);
00477     }
00478 
00479     return NULL;
00480 }
00481 
00482 static guint
00483 xmms_magic_complexity (GNode *tree)
00484 {
00485     return g_node_n_nodes (tree, G_TRAVERSE_ALL);
00486 }
00487 
00488 static gint
00489 cb_sort_magic_list (GNode *a, GNode *b)
00490 {
00491     guint n1, n2;
00492 
00493     n1 = xmms_magic_complexity (a);
00494     n2 = xmms_magic_complexity (b);
00495 
00496     if (n1 > n2) {
00497         return -1;
00498     } else if (n1 < n2) {
00499         return 1;
00500     } else {
00501         return 0;
00502     }
00503 }
00504 
00505 
00506 gboolean
00507 xmms_magic_extension_add (const gchar *mime, const gchar *ext)
00508 {
00509     xmms_magic_ext_data_t *e;
00510 
00511     g_return_val_if_fail (mime, FALSE);
00512     g_return_val_if_fail (ext, FALSE);
00513 
00514     e = g_new0 (xmms_magic_ext_data_t, 1);
00515     e->pattern = g_strdup (ext);
00516     e->type = g_strdup (mime);
00517 
00518     ext_list = g_list_prepend (ext_list, e);
00519 
00520     return TRUE;
00521 }
00522 
00523 gboolean
00524 xmms_magic_add (const gchar *desc, const gchar *mime, ...)
00525 {
00526     GNode *tree, *node = NULL;
00527     va_list ap;
00528     gchar *s;
00529     gpointer *root_props;
00530     gboolean ret = TRUE;
00531 
00532     g_return_val_if_fail (desc, FALSE);
00533     g_return_val_if_fail (mime, FALSE);
00534 
00535     /* now process the magic specs in the argument list */
00536     va_start (ap, mime);
00537 
00538     s = va_arg (ap, gchar *);
00539     if (!s) { /* no magic specs passed -> failure */
00540         va_end (ap);
00541         return FALSE;
00542     }
00543 
00544     /* root node stores the description and the mimetype */
00545     root_props = g_new0 (gpointer, 2);
00546     root_props[0] = g_strdup (desc);
00547     root_props[1] = g_strdup (mime);
00548     tree = g_node_new (root_props);
00549 
00550     do {
00551         if (!*s) {
00552             ret = FALSE;
00553             xmms_log_error ("invalid magic spec: '%s'", s);
00554             break;
00555         }
00556 
00557         s = g_strdup (s); /* we need our own copy */
00558         node = xmms_magic_add_node (tree, s, node);
00559         g_free (s);
00560 
00561         if (!node) {
00562             xmms_log_error ("invalid magic spec: '%s'", s);
00563             ret = FALSE;
00564             break;
00565         }
00566     } while ((s = va_arg (ap, gchar *)));
00567 
00568     va_end (ap);
00569 
00570     /* only add this tree to the list if all spec chunks are valid */
00571     if (ret) {
00572         magic_list =
00573             g_list_insert_sorted (magic_list, tree,
00574                                   (GCompareFunc) cb_sort_magic_list);
00575     } else {
00576         xmms_magic_tree_free (tree);
00577     }
00578 
00579     return ret;
00580 }
00581 
00582 static gboolean
00583 xmms_magic_plugin_init (xmms_xform_t *xform)
00584 {
00585     xmms_magic_checker_t c;
00586     gchar *res;
00587     const gchar *url;
00588     xmms_config_property_t *cv;
00589 
00590     c.xform = xform;
00591     c.read = c.offset = 0;
00592     c.alloc = 128; /* start with a 128 bytes buffer */
00593     c.buf = g_malloc (c.alloc);
00594 
00595     cv = xmms_xform_config_lookup (xform, "dumpcount");
00596     c.dumpcount = xmms_config_property_get_int (cv);
00597 
00598     url = xmms_xform_indata_find_str (xform, XMMS_STREAM_TYPE_URL);
00599 
00600     res = xmms_magic_match (&c, url);
00601     if (res) {
00602         xmms_xform_metadata_set_str (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_MIME, res);
00603         xmms_xform_outdata_type_add (xform,
00604                                      XMMS_STREAM_TYPE_MIMETYPE,
00605                                      res,
00606                                      XMMS_STREAM_TYPE_END);
00607     }
00608 
00609     g_free (c.buf);
00610 
00611     return !!res;
00612 }
00613 
00614 static gboolean
00615 xmms_magic_plugin_setup (xmms_xform_plugin_t *xform_plugin)
00616 {
00617     xmms_xform_methods_t methods;
00618 
00619     XMMS_XFORM_METHODS_INIT (methods);
00620     methods.init = xmms_magic_plugin_init;
00621     methods.read = xmms_xform_read;
00622     methods.seek = xmms_xform_seek;
00623 
00624     xmms_xform_plugin_methods_set (xform_plugin, &methods);
00625 
00626     xmms_xform_plugin_indata_add (xform_plugin,
00627                                   XMMS_STREAM_TYPE_MIMETYPE,
00628                                   "application/octet-stream",
00629                                   XMMS_STREAM_TYPE_END);
00630 
00631     xmms_xform_plugin_config_property_register (xform_plugin, "dumpcount",
00632                                                 "16", NULL, NULL);
00633 
00634     return TRUE;
00635 }
00636 
00637 XMMS_XFORM_BUILTIN (magic,
00638                     "Magic file identifier",
00639                     XMMS_VERSION,
00640                     "Magic file identifier",
00641                     xmms_magic_plugin_setup);