• Main Page
  • Related Pages
  • Modules
  • Data Structures
  • Files
  • File List
  • Globals

src/xmms/magic.c

Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2009 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 
00019 #include <glib.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 } xmms_magic_checker_t;
00100 
00101 typedef struct xmms_magic_ext_data_St {
00102     gchar *type;
00103     gchar *pattern;
00104 } xmms_magic_ext_data_t;
00105 
00106 static void xmms_magic_tree_free (GNode *tree);
00107 
00108 static gchar *xmms_magic_match (xmms_magic_checker_t *c, const gchar *u);
00109 static guint xmms_magic_complexity (GNode *tree);
00110 
00111 static void
00112 xmms_magic_entry_free (xmms_magic_entry_t *e)
00113 {
00114     g_free (e);
00115 }
00116 
00117 static xmms_magic_entry_type_t
00118 parse_type (gchar **s, gint *endian)
00119 {
00120     struct {
00121         const gchar *string;
00122         xmms_magic_entry_type_t type;
00123         gint endian;
00124     } *t, types[] = {
00125         {"byte", XMMS_MAGIC_ENTRY_TYPE_BYTE, G_BYTE_ORDER},
00126         {"short", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
00127         {"long", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
00128         {"beshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BIG_ENDIAN},
00129         {"belong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_BIG_ENDIAN},
00130         {"leshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_LITTLE_ENDIAN},
00131         {"lelong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_LITTLE_ENDIAN},
00132         {"string/c", XMMS_MAGIC_ENTRY_TYPE_STRINGC, G_BYTE_ORDER},
00133         {"string", XMMS_MAGIC_ENTRY_TYPE_STRING, G_BYTE_ORDER},
00134         {NULL, XMMS_MAGIC_ENTRY_TYPE_UNKNOWN, G_BYTE_ORDER}
00135     };
00136 
00137     for (t = types; t; t++) {
00138         int l = t->string ? strlen (t->string) : 0;
00139 
00140         if (!l || !strncmp (*s, t->string, l)) {
00141             *s += l;
00142             *endian = t->endian;
00143 
00144             return t->type;
00145         }
00146     }
00147 
00148     g_assert_not_reached ();
00149 }
00150 
00151 
00152 static xmms_magic_entry_operator_t
00153 parse_oper (gchar **s)
00154 {
00155     gchar c = **s;
00156     struct {
00157         gchar c;
00158         xmms_magic_entry_operator_t o;
00159     } *o, opers[] = {
00160         {'=', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL},
00161         {'<', XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN},
00162         {'>', XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN},
00163         {'&', XMMS_MAGIC_ENTRY_OPERATOR_AND},
00164         {'^', XMMS_MAGIC_ENTRY_OPERATOR_NAND},
00165         {'\0', XMMS_MAGIC_ENTRY_OPERATOR_EQUAL}
00166     };
00167 
00168     for (o = opers; o; o++) {
00169         if (!o->c) {
00170             /* no operator found */
00171             return o->o;
00172         } else if (c == o->c) {
00173             (*s)++; /* skip operator */
00174             return o->o;
00175         }
00176     }
00177 
00178     g_assert_not_reached ();
00179 }
00180 
00181 static gboolean
00182 parse_pre_test_and_op (xmms_magic_entry_t *entry, gchar **end)
00183 {
00184     gboolean ret = FALSE;
00185 
00186     if (**end == ' ') {
00187         (*end)++;
00188         return TRUE;
00189     }
00190 
00191     switch (entry->type) {
00192         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00193         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00194         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00195             if (**end == '&') {
00196                 (*end)++;
00197                 entry->pre_test_and_op = strtoul (*end, end, 0);
00198                 ret = TRUE;
00199             }
00200         default:
00201             break;
00202     }
00203 
00204     return ret;
00205 }
00206 
00207 static xmms_magic_entry_t *
00208 parse_entry (const gchar *s)
00209 {
00210     xmms_magic_entry_t *entry;
00211     gchar *end = NULL;
00212 
00213     entry = g_new0 (xmms_magic_entry_t, 1);
00214     entry->endian = G_BYTE_ORDER;
00215     entry->oper = XMMS_MAGIC_ENTRY_OPERATOR_EQUAL;
00216     entry->offset = strtoul (s, &end, 0);
00217 
00218     end++;
00219 
00220     entry->type = parse_type (&end, &entry->endian);
00221     if (entry->type == XMMS_MAGIC_ENTRY_TYPE_UNKNOWN) {
00222         g_free (entry);
00223         return NULL;
00224     }
00225 
00226     if (!parse_pre_test_and_op (entry, &end)) {
00227         g_free (entry);
00228         return NULL;
00229     }
00230 
00231     /* @todo Implement string operators */
00232     switch (entry->type) {
00233         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00234         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00235             break;
00236         default:
00237             entry->oper = parse_oper (&end);
00238             break;
00239     }
00240 
00241     switch (entry->type) {
00242         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00243             entry->value.i8 = strtoul (end, &end, 0);
00244             entry->len = 1;
00245             break;
00246         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00247             entry->value.i16 = strtoul (end, &end, 0);
00248             entry->len = 2;
00249             break;
00250         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00251             entry->value.i32 = strtoul (end, &end, 0);
00252             entry->len = 4;
00253             break;
00254         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00255         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00256             g_strlcpy (entry->value.s, end, sizeof (entry->value.s));
00257             entry->len = strlen (entry->value.s);
00258             break;
00259         default:
00260             break; /* won't get here, handled above */
00261     }
00262 
00263     return entry;
00264 }
00265 
00266 static gboolean
00267 free_node (GNode *node, xmms_magic_entry_t *entry)
00268 {
00269     if (G_NODE_IS_ROOT (node)) {
00270         gpointer *data = node->data;
00271 
00272         /* this isn't a magic entry, but the description of the tree */
00273         g_free (data[0]); /* desc */
00274         g_free (data[1]); /* mime */
00275         g_free (data);
00276     } else {
00277         xmms_magic_entry_free (entry);
00278     }
00279 
00280     return FALSE; /* continue traversal */
00281 }
00282 
00283 static void
00284 xmms_magic_tree_free (GNode *tree)
00285 {
00286     g_node_traverse (tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
00287                      (GNodeTraverseFunc) free_node, NULL);
00288 }
00289 
00290 static GNode *
00291 xmms_magic_add_node (GNode *tree, const gchar *s, GNode *prev_node)
00292 {
00293     xmms_magic_entry_t *entry;
00294     gpointer *data = tree->data;
00295     guint indent = 0, prev_indent;
00296 
00297     g_assert (s);
00298 
00299     XMMS_DBG ("adding magic spec to tree '%s'", (gchar *) data[0]);
00300 
00301     /* indent level is number of leading '>' characters */
00302     while (*s == '>') {
00303         indent++;
00304         s++;
00305     }
00306 
00307     entry = parse_entry (s);
00308     if (!entry) {
00309         XMMS_DBG ("cannot parse magic entry");
00310         return NULL;
00311     }
00312 
00313     if (!indent) {
00314         return g_node_append_data (tree, entry);
00315     }
00316 
00317     if (!prev_node) {
00318         XMMS_DBG ("invalid indent level");
00319         xmms_magic_entry_free (entry);
00320         return NULL;
00321     }
00322 
00323     prev_indent = g_node_depth (prev_node) - 2;
00324 
00325     if (indent > prev_indent) {
00326         /* larger jumps are invalid */
00327         if (indent != prev_indent + 1) {
00328             XMMS_DBG ("invalid indent level");
00329             xmms_magic_entry_free (entry);
00330             return NULL;
00331         }
00332 
00333         return g_node_append_data (prev_node, entry);
00334     } else {
00335         while (indent < prev_indent) {
00336             prev_indent--;
00337             prev_node = prev_node->parent;
00338         }
00339 
00340         return g_node_insert_after (prev_node->parent, prev_node,
00341                                     g_node_new (entry));
00342     }
00343 }
00344 
00345 static gint
00346 read_data (xmms_magic_checker_t *c, guint needed)
00347 {
00348     xmms_error_t e;
00349 
00350     if (needed > c->alloc) {
00351         c->alloc = needed;
00352         c->buf = g_realloc (c->buf, c->alloc);
00353     }
00354 
00355     xmms_error_reset (&e);
00356 
00357     return xmms_xform_peek (c->xform, c->buf, needed, &e);
00358 }
00359 
00360 static gboolean
00361 node_match (xmms_magic_checker_t *c, GNode *node)
00362 {
00363     xmms_magic_entry_t *entry = node->data;
00364     guint needed = c->offset + entry->offset + entry->len;
00365     guint8 i8;
00366     guint16 i16;
00367     guint32 i32;
00368     gint tmp;
00369     gchar *ptr;
00370 
00371     /* do we have enough data ready for this check?
00372      * if not, read some more
00373      */
00374     if (c->read < needed) {
00375         tmp = read_data (c, needed);
00376         if (tmp == -1) {
00377             return FALSE;
00378         }
00379 
00380         c->read = tmp;
00381         if (c->read < needed) {
00382             /* couldn't read enough data */
00383             return FALSE;
00384         }
00385     }
00386 
00387     ptr = &c->buf[c->offset + entry->offset];
00388 
00389     switch (entry->type) {
00390         case XMMS_MAGIC_ENTRY_TYPE_BYTE:
00391             memcpy (&i8, ptr, sizeof (i8));
00392             CMP (i8, entry, entry->value.i8); /* returns */
00393         case XMMS_MAGIC_ENTRY_TYPE_INT16:
00394             memcpy (&i16, ptr, sizeof (i16));
00395             SWAP16 (i16, entry->endian);
00396             CMP (i16, entry, entry->value.i16); /* returns */
00397         case XMMS_MAGIC_ENTRY_TYPE_INT32:
00398             memcpy (&i32, ptr, sizeof (i32));
00399             SWAP32 (i32, entry->endian);
00400             CMP (i32, entry, entry->value.i32); /* returns */
00401         case XMMS_MAGIC_ENTRY_TYPE_STRING:
00402             return !strncmp (ptr, entry->value.s, entry->len);
00403         case XMMS_MAGIC_ENTRY_TYPE_STRINGC:
00404             return !g_ascii_strncasecmp (ptr, entry->value.s, entry->len);
00405         default:
00406             return FALSE;
00407     }
00408 }
00409 
00410 static gboolean
00411 tree_match (xmms_magic_checker_t *c, GNode *tree)
00412 {
00413     GNode *n;
00414 
00415     /* empty subtrees match anything */
00416     if (!tree->children) {
00417         return TRUE;
00418     }
00419 
00420     for (n = tree->children; n; n = n->next) {
00421         if (node_match (c, n) && tree_match (c, n)) {
00422             return TRUE;
00423         }
00424     }
00425 
00426     return FALSE;
00427 }
00428 
00429 static gchar *
00430 xmms_magic_match (xmms_magic_checker_t *c, const gchar *uri)
00431 {
00432     const GList *l;
00433     gchar *u;
00434 
00435     g_return_val_if_fail (c, NULL);
00436 
00437     /* only one of the contained sets has to match */
00438     for (l = magic_list; l; l = g_list_next (l)) {
00439         GNode *tree = l->data;
00440 
00441         if (tree_match (c, tree)) {
00442             gpointer *data = tree->data;
00443             XMMS_DBG ("magic plugin detected '%s' (%s)",
00444                       (char *)data[1], (char *)data[0]);
00445             return (char *) (data[1]);
00446         }
00447     }
00448 
00449     if (!uri)
00450         return NULL;
00451 
00452     u = g_ascii_strdown (uri, -1);
00453     for (l = ext_list; l; l = g_list_next (l)) {
00454         xmms_magic_ext_data_t *e = l->data;
00455         if (g_pattern_match_simple (e->pattern, u)) {
00456             XMMS_DBG ("magic plugin detected '%s' (by extension '%s')", e->type, e->pattern);
00457             g_free (u);
00458             return e->type;
00459         }
00460     }
00461     g_free (u);
00462 
00463     return NULL;
00464 }
00465 
00466 static guint
00467 xmms_magic_complexity (GNode *tree)
00468 {
00469     return g_node_n_nodes (tree, G_TRAVERSE_ALL);
00470 }
00471 
00472 static gint
00473 cb_sort_magic_list (GNode *a, GNode *b)
00474 {
00475     guint n1, n2;
00476 
00477     n1 = xmms_magic_complexity (a);
00478     n2 = xmms_magic_complexity (b);
00479 
00480     if (n1 > n2) {
00481         return -1;
00482     } else if (n1 < n2) {
00483         return 1;
00484     } else {
00485         return 0;
00486     }
00487 }
00488 
00489 
00490 gboolean
00491 xmms_magic_extension_add (const gchar *mime, const gchar *ext)
00492 {
00493     xmms_magic_ext_data_t *e;
00494 
00495     g_return_val_if_fail (mime, FALSE);
00496     g_return_val_if_fail (ext, FALSE);
00497 
00498     e = g_new0 (xmms_magic_ext_data_t, 1);
00499     e->pattern = g_strdup (ext);
00500     e->type = g_strdup (mime);
00501 
00502     ext_list = g_list_prepend (ext_list, e);
00503 
00504     return TRUE;
00505 }
00506 
00507 gboolean
00508 xmms_magic_add (const gchar *desc, const gchar *mime, ...)
00509 {
00510     GNode *tree, *node = NULL;
00511     va_list ap;
00512     gchar *s;
00513     gpointer *root_props;
00514     gboolean ret = TRUE;
00515 
00516     g_return_val_if_fail (desc, FALSE);
00517     g_return_val_if_fail (mime, FALSE);
00518 
00519     /* now process the magic specs in the argument list */
00520     va_start (ap, mime);
00521 
00522     s = va_arg (ap, gchar *);
00523     if (!s) { /* no magic specs passed -> failure */
00524         va_end (ap);
00525         return FALSE;
00526     }
00527 
00528     /* root node stores the description and the mimetype */
00529     root_props = g_new0 (gpointer, 2);
00530     root_props[0] = g_strdup (desc);
00531     root_props[1] = g_strdup (mime);
00532     tree = g_node_new (root_props);
00533 
00534     do {
00535         if (!*s) {
00536             ret = FALSE;
00537             xmms_log_error ("invalid magic spec: '%s'", s);
00538             break;
00539         }
00540 
00541         s = g_strdup (s); /* we need our own copy */
00542         node = xmms_magic_add_node (tree, s, node);
00543         g_free (s);
00544 
00545         if (!node) {
00546             xmms_log_error ("invalid magic spec: '%s'", s);
00547             ret = FALSE;
00548             break;
00549         }
00550     } while ((s = va_arg (ap, gchar *)));
00551 
00552     va_end (ap);
00553 
00554     /* only add this tree to the list if all spec chunks are valid */
00555     if (ret) {
00556         magic_list =
00557             g_list_insert_sorted (magic_list, tree,
00558                                   (GCompareFunc) cb_sort_magic_list);
00559     } else {
00560         xmms_magic_tree_free (tree);
00561     }
00562 
00563     return ret;
00564 }
00565 
00566 static gboolean
00567 xmms_magic_plugin_init (xmms_xform_t *xform)
00568 {
00569     xmms_magic_checker_t c;
00570     gchar *res;
00571     const gchar *url;
00572 
00573     c.xform = xform;
00574     c.read = c.offset = 0;
00575     c.alloc = 128; /* start with a 128 bytes buffer */
00576     c.buf = g_malloc (c.alloc);
00577 
00578     url = xmms_xform_indata_find_str (xform, XMMS_STREAM_TYPE_URL);
00579 
00580     res = xmms_magic_match (&c, url);
00581     if (res) {
00582         xmms_xform_metadata_set_str (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_MIME, res);
00583         xmms_xform_outdata_type_add (xform,
00584                                      XMMS_STREAM_TYPE_MIMETYPE,
00585                                      res,
00586                                      XMMS_STREAM_TYPE_END);
00587     }
00588 
00589     g_free (c.buf);
00590 
00591     return !!res;
00592 }
00593 
00594 static gboolean
00595 xmms_magic_plugin_setup (xmms_xform_plugin_t *xform_plugin)
00596 {
00597     xmms_xform_methods_t methods;
00598 
00599     XMMS_XFORM_METHODS_INIT (methods);
00600     methods.init = xmms_magic_plugin_init;
00601     methods.read = xmms_xform_read;
00602     methods.seek = xmms_xform_seek;
00603 
00604     xmms_xform_plugin_methods_set (xform_plugin, &methods);
00605 
00606     xmms_xform_plugin_indata_add (xform_plugin,
00607                                   XMMS_STREAM_TYPE_MIMETYPE,
00608                                   "application/octet-stream",
00609                                   XMMS_STREAM_TYPE_END);
00610 
00611     return TRUE;
00612 }
00613 
00614 XMMS_XFORM_BUILTIN (magic,
00615                     "Magic file identifier",
00616                     XMMS_VERSION,
00617                     "Magic file identifier",
00618                     xmms_magic_plugin_setup);

Generated on Wed Feb 9 2011 for XMMS2 by  doxygen 1.7.1