XMMS2
magic.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2011 XMMS2 Team
3  *
4  * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  */
16 
17 
18 #include <glib.h>
19 #include <glib/gprintf.h>
20 #include <string.h>
21 #include <stdlib.h>
22 
23 #include "xmms/xmms_log.h"
24 #include "xmmspriv/xmms_xform.h"
25 
26 static GList *magic_list, *ext_list;
27 
28 #define SWAP16(v, endian) \
29  if (endian == G_LITTLE_ENDIAN) { \
30  v = GUINT16_TO_LE (v); \
31  } else if (endian == G_BIG_ENDIAN) { \
32  v = GUINT16_TO_BE (v); \
33  }
34 
35 #define SWAP32(v, endian) \
36  if (endian == G_LITTLE_ENDIAN) { \
37  v = GUINT32_TO_LE (v); \
38  } else if (endian == G_BIG_ENDIAN) { \
39  v = GUINT32_TO_BE (v); \
40  }
41 
42 #define CMP(v1, entry, v2) \
43  if (entry->pre_test_and_op) { \
44  v1 &= entry->pre_test_and_op; \
45  } \
46 \
47  switch (entry->oper) { \
48  case XMMS_MAGIC_ENTRY_OPERATOR_EQUAL: \
49  return v1 == v2; \
50  case XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN: \
51  return v1 < v2; \
52  case XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN: \
53  return v1 > v2; \
54  case XMMS_MAGIC_ENTRY_OPERATOR_AND: \
55  return (v1 & v2) == v2; \
56  case XMMS_MAGIC_ENTRY_OPERATOR_NAND: \
57  return (v1 & v2) != v2; \
58  } \
59 
68 
76 
77 typedef struct xmms_magic_entry_St {
78  guint offset;
80  gint endian;
81  guint len;
82  guint pre_test_and_op;
84 
85  union {
86  guint8 i8;
87  guint16 i16;
88  guint32 i32;
89  gchar s[32];
90  } value;
92 
93 typedef struct xmms_magic_checker_St {
94  xmms_xform_t *xform;
95  gchar *buf;
96  guint alloc;
97  guint read;
98  guint offset;
99  gint dumpcount;
101 
102 typedef struct xmms_magic_ext_data_St {
103  gchar *type;
104  gchar *pattern;
106 
107 static void xmms_magic_tree_free (GNode *tree);
108 
109 static gchar *xmms_magic_match (xmms_magic_checker_t *c, const gchar *u);
110 static guint xmms_magic_complexity (GNode *tree);
111 
112 static void
113 xmms_magic_entry_free (xmms_magic_entry_t *e)
114 {
115  g_free (e);
116 }
117 
119 parse_type (gchar **s, gint *endian)
120 {
121  struct {
122  const gchar *string;
124  gint endian;
125  } *t, types[] = {
126  {"byte", XMMS_MAGIC_ENTRY_TYPE_BYTE, G_BYTE_ORDER},
127  {"short", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
128  {"long", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BYTE_ORDER},
129  {"beshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_BIG_ENDIAN},
130  {"belong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_BIG_ENDIAN},
131  {"leshort", XMMS_MAGIC_ENTRY_TYPE_INT16, G_LITTLE_ENDIAN},
132  {"lelong", XMMS_MAGIC_ENTRY_TYPE_INT32, G_LITTLE_ENDIAN},
133  {"string/c", XMMS_MAGIC_ENTRY_TYPE_STRINGC, G_BYTE_ORDER},
134  {"string", XMMS_MAGIC_ENTRY_TYPE_STRING, G_BYTE_ORDER},
135  {NULL, XMMS_MAGIC_ENTRY_TYPE_UNKNOWN, G_BYTE_ORDER}
136  };
137 
138  for (t = types; t; t++) {
139  int l = t->string ? strlen (t->string) : 0;
140 
141  if (!l || !strncmp (*s, t->string, l)) {
142  *s += l;
143  *endian = t->endian;
144 
145  return t->type;
146  }
147  }
148 
149  g_assert_not_reached ();
150 }
151 
152 
154 parse_oper (gchar **s)
155 {
156  gchar c = **s;
157  struct {
158  gchar c;
160  } *o, opers[] = {
167  };
168 
169  for (o = opers; o; o++) {
170  if (!o->c) {
171  /* no operator found */
172  return o->o;
173  } else if (c == o->c) {
174  (*s)++; /* skip operator */
175  return o->o;
176  }
177  }
178 
179  g_assert_not_reached ();
180 }
181 
182 static gboolean
183 parse_pre_test_and_op (xmms_magic_entry_t *entry, gchar **end)
184 {
185  gboolean ret = FALSE;
186 
187  if (**end == ' ') {
188  (*end)++;
189  return TRUE;
190  }
191 
192  switch (entry->type) {
196  if (**end == '&') {
197  (*end)++;
198  entry->pre_test_and_op = strtoul (*end, end, 0);
199  ret = TRUE;
200  }
201  default:
202  break;
203  }
204 
205  return ret;
206 }
207 
208 static xmms_magic_entry_t *
209 parse_entry (const gchar *s)
210 {
211  xmms_magic_entry_t *entry;
212  gchar *end = NULL;
213 
214  entry = g_new0 (xmms_magic_entry_t, 1);
215  entry->endian = G_BYTE_ORDER;
216  entry->oper = XMMS_MAGIC_ENTRY_OPERATOR_EQUAL;
217  entry->offset = strtoul (s, &end, 0);
218 
219  end++;
220 
221  entry->type = parse_type (&end, &entry->endian);
222  if (entry->type == XMMS_MAGIC_ENTRY_TYPE_UNKNOWN) {
223  g_free (entry);
224  return NULL;
225  }
226 
227  if (!parse_pre_test_and_op (entry, &end)) {
228  g_free (entry);
229  return NULL;
230  }
231 
232  /* @todo Implement string operators */
233  switch (entry->type) {
236  break;
237  default:
238  entry->oper = parse_oper (&end);
239  break;
240  }
241 
242  switch (entry->type) {
244  entry->value.i8 = strtoul (end, &end, 0);
245  entry->len = 1;
246  break;
248  entry->value.i16 = strtoul (end, &end, 0);
249  entry->len = 2;
250  break;
252  entry->value.i32 = strtoul (end, &end, 0);
253  entry->len = 4;
254  break;
257  g_strlcpy (entry->value.s, end, sizeof (entry->value.s));
258  entry->len = strlen (entry->value.s);
259  break;
260  default:
261  break; /* won't get here, handled above */
262  }
263 
264  return entry;
265 }
266 
267 static gboolean
268 free_node (GNode *node, xmms_magic_entry_t *entry)
269 {
270  if (G_NODE_IS_ROOT (node)) {
271  gpointer *data = node->data;
272 
273  /* this isn't a magic entry, but the description of the tree */
274  g_free (data[0]); /* desc */
275  g_free (data[1]); /* mime */
276  g_free (data);
277  } else {
278  xmms_magic_entry_free (entry);
279  }
280 
281  return FALSE; /* continue traversal */
282 }
283 
284 static void
285 xmms_magic_tree_free (GNode *tree)
286 {
287  g_node_traverse (tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
288  (GNodeTraverseFunc) free_node, NULL);
289 }
290 
291 static GNode *
292 xmms_magic_add_node (GNode *tree, const gchar *s, GNode *prev_node)
293 {
294  xmms_magic_entry_t *entry;
295  gpointer *data = tree->data;
296  guint indent = 0, prev_indent;
297 
298  g_assert (s);
299 
300  XMMS_DBG ("adding magic spec to tree '%s'", (gchar *) data[0]);
301 
302  /* indent level is number of leading '>' characters */
303  while (*s == '>') {
304  indent++;
305  s++;
306  }
307 
308  entry = parse_entry (s);
309  if (!entry) {
310  XMMS_DBG ("cannot parse magic entry");
311  return NULL;
312  }
313 
314  if (!indent) {
315  return g_node_append_data (tree, entry);
316  }
317 
318  if (!prev_node) {
319  XMMS_DBG ("invalid indent level");
320  xmms_magic_entry_free (entry);
321  return NULL;
322  }
323 
324  prev_indent = g_node_depth (prev_node) - 2;
325 
326  if (indent > prev_indent) {
327  /* larger jumps are invalid */
328  if (indent != prev_indent + 1) {
329  XMMS_DBG ("invalid indent level");
330  xmms_magic_entry_free (entry);
331  return NULL;
332  }
333 
334  return g_node_append_data (prev_node, entry);
335  } else {
336  while (indent < prev_indent) {
337  prev_indent--;
338  prev_node = prev_node->parent;
339  }
340 
341  return g_node_insert_after (prev_node->parent, prev_node,
342  g_node_new (entry));
343  }
344 }
345 
346 static gint
347 read_data (xmms_magic_checker_t *c, guint needed)
348 {
349  xmms_error_t e;
350 
351  if (needed > c->alloc) {
352  c->alloc = needed;
353  c->buf = g_realloc (c->buf, c->alloc);
354  }
355 
356  xmms_error_reset (&e);
357 
358  return xmms_xform_peek (c->xform, c->buf, needed, &e);
359 }
360 
361 static gboolean
362 node_match (xmms_magic_checker_t *c, GNode *node)
363 {
364  xmms_magic_entry_t *entry = node->data;
365  guint needed = c->offset + entry->offset + entry->len;
366  guint8 i8;
367  guint16 i16;
368  guint32 i32;
369  gint tmp;
370  gchar *ptr;
371 
372  /* do we have enough data ready for this check?
373  * if not, read some more
374  */
375  if (c->read < needed) {
376  tmp = read_data (c, needed);
377  if (tmp == -1) {
378  return FALSE;
379  }
380 
381  c->read = tmp;
382  if (c->read < needed) {
383  /* couldn't read enough data */
384  return FALSE;
385  }
386  }
387 
388  ptr = &c->buf[c->offset + entry->offset];
389 
390  switch (entry->type) {
392  memcpy (&i8, ptr, sizeof (i8));
393  CMP (i8, entry, entry->value.i8); /* returns */
395  memcpy (&i16, ptr, sizeof (i16));
396  SWAP16 (i16, entry->endian);
397  CMP (i16, entry, entry->value.i16); /* returns */
399  memcpy (&i32, ptr, sizeof (i32));
400  SWAP32 (i32, entry->endian);
401  CMP (i32, entry, entry->value.i32); /* returns */
403  return !strncmp (ptr, entry->value.s, entry->len);
405  return !g_ascii_strncasecmp (ptr, entry->value.s, entry->len);
406  default:
407  return FALSE;
408  }
409 }
410 
411 static gboolean
412 tree_match (xmms_magic_checker_t *c, GNode *tree)
413 {
414  GNode *n;
415 
416  /* empty subtrees match anything */
417  if (!tree->children) {
418  return TRUE;
419  }
420 
421  for (n = tree->children; n; n = n->next) {
422  if (node_match (c, n) && tree_match (c, n)) {
423  return TRUE;
424  }
425  }
426 
427  return FALSE;
428 }
429 
430 static gchar *
431 xmms_magic_match (xmms_magic_checker_t *c, const gchar *uri)
432 {
433  const GList *l;
434  gchar *u, *dump;
435  int i;
436 
437  g_return_val_if_fail (c, NULL);
438 
439  /* only one of the contained sets has to match */
440  for (l = magic_list; l; l = g_list_next (l)) {
441  GNode *tree = l->data;
442 
443  if (tree_match (c, tree)) {
444  gpointer *data = tree->data;
445  XMMS_DBG ("magic plugin detected '%s' (%s)",
446  (char *)data[1], (char *)data[0]);
447  return (char *) (data[1]);
448  }
449  }
450 
451  if (!uri)
452  return NULL;
453 
454  u = g_ascii_strdown (uri, -1);
455  for (l = ext_list; l; l = g_list_next (l)) {
456  xmms_magic_ext_data_t *e = l->data;
457  if (g_pattern_match_simple (e->pattern, u)) {
458  XMMS_DBG ("magic plugin detected '%s' (by extension '%s')", e->type, e->pattern);
459  g_free (u);
460  return e->type;
461  }
462  }
463  g_free (u);
464 
465  if (c->dumpcount > 0) {
466  dump = g_malloc ((MIN (c->read, c->dumpcount) * 3) + 1);
467  u = dump;
468 
469  XMMS_DBG ("Magic didn't match anything...");
470  for (i = 0; i < c->dumpcount && i < c->read; i++) {
471  g_sprintf (u, "%02X ", (unsigned char)c->buf[i]);
472  u += 3;
473  }
474  XMMS_DBG ("%s", dump);
475 
476  g_free (dump);
477  }
478 
479  return NULL;
480 }
481 
482 static guint
483 xmms_magic_complexity (GNode *tree)
484 {
485  return g_node_n_nodes (tree, G_TRAVERSE_ALL);
486 }
487 
488 static gint
489 cb_sort_magic_list (GNode *a, GNode *b)
490 {
491  guint n1, n2;
492 
493  n1 = xmms_magic_complexity (a);
494  n2 = xmms_magic_complexity (b);
495 
496  if (n1 > n2) {
497  return -1;
498  } else if (n1 < n2) {
499  return 1;
500  } else {
501  return 0;
502  }
503 }
504 
505 
506 gboolean
507 xmms_magic_extension_add (const gchar *mime, const gchar *ext)
508 {
510 
511  g_return_val_if_fail (mime, FALSE);
512  g_return_val_if_fail (ext, FALSE);
513 
514  e = g_new0 (xmms_magic_ext_data_t, 1);
515  e->pattern = g_strdup (ext);
516  e->type = g_strdup (mime);
517 
518  ext_list = g_list_prepend (ext_list, e);
519 
520  return TRUE;
521 }
522 
523 gboolean
524 xmms_magic_add (const gchar *desc, const gchar *mime, ...)
525 {
526  GNode *tree, *node = NULL;
527  va_list ap;
528  gchar *s;
529  gpointer *root_props;
530  gboolean ret = TRUE;
531 
532  g_return_val_if_fail (desc, FALSE);
533  g_return_val_if_fail (mime, FALSE);
534 
535  /* now process the magic specs in the argument list */
536  va_start (ap, mime);
537 
538  s = va_arg (ap, gchar *);
539  if (!s) { /* no magic specs passed -> failure */
540  va_end (ap);
541  return FALSE;
542  }
543 
544  /* root node stores the description and the mimetype */
545  root_props = g_new0 (gpointer, 2);
546  root_props[0] = g_strdup (desc);
547  root_props[1] = g_strdup (mime);
548  tree = g_node_new (root_props);
549 
550  do {
551  if (!*s) {
552  ret = FALSE;
553  xmms_log_error ("invalid magic spec: '%s'", s);
554  break;
555  }
556 
557  s = g_strdup (s); /* we need our own copy */
558  node = xmms_magic_add_node (tree, s, node);
559  g_free (s);
560 
561  if (!node) {
562  xmms_log_error ("invalid magic spec: '%s'", s);
563  ret = FALSE;
564  break;
565  }
566  } while ((s = va_arg (ap, gchar *)));
567 
568  va_end (ap);
569 
570  /* only add this tree to the list if all spec chunks are valid */
571  if (ret) {
572  magic_list =
573  g_list_insert_sorted (magic_list, tree,
574  (GCompareFunc) cb_sort_magic_list);
575  } else {
576  xmms_magic_tree_free (tree);
577  }
578 
579  return ret;
580 }
581 
582 static gboolean
583 xmms_magic_plugin_init (xmms_xform_t *xform)
584 {
586  gchar *res;
587  const gchar *url;
589 
590  c.xform = xform;
591  c.read = c.offset = 0;
592  c.alloc = 128; /* start with a 128 bytes buffer */
593  c.buf = g_malloc (c.alloc);
594 
595  cv = xmms_xform_config_lookup (xform, "dumpcount");
596  c.dumpcount = xmms_config_property_get_int (cv);
597 
599 
600  res = xmms_magic_match (&c, url);
601  if (res) {
605  res,
607  }
608 
609  g_free (c.buf);
610 
611  return !!res;
612 }
613 
614 static gboolean
615 xmms_magic_plugin_setup (xmms_xform_plugin_t *xform_plugin)
616 {
617  xmms_xform_methods_t methods;
618 
619  XMMS_XFORM_METHODS_INIT (methods);
620  methods.init = xmms_magic_plugin_init;
621  methods.read = xmms_xform_read;
622  methods.seek = xmms_xform_seek;
623 
624  xmms_xform_plugin_methods_set (xform_plugin, &methods);
625 
626  xmms_xform_plugin_indata_add (xform_plugin,
628  "application/octet-stream",
630 
631  xmms_xform_plugin_config_property_register (xform_plugin, "dumpcount",
632  "16", NULL, NULL);
633 
634  return TRUE;
635 }
636 
637 XMMS_XFORM_BUILTIN (magic,
638  "Magic file identifier",
639  XMMS_VERSION,
640  "Magic file identifier",
641  xmms_magic_plugin_setup);
struct xmms_xform_plugin_St xmms_xform_plugin_t
Xform plugin.
xmms_magic_entry_type_St
Definition: magic.c:60
gboolean xmms_magic_add(const gchar *desc, const gchar *mime,...)
Definition: magic.c:524
#define XMMS_MEDIALIB_ENTRY_PROPERTY_MIME
Definition: xmms_medialib.h:27
void xmms_xform_plugin_indata_add(xmms_xform_plugin_t *plugin,...)
Add a valid input type to the plugin.
Definition: xform_plugin.c:79
struct xmms_magic_entry_St xmms_magic_entry_t
enum xmms_magic_entry_type_St xmms_magic_entry_type_t
struct xmms_xform_St xmms_xform_t
gboolean(* init)(xmms_xform_t *)
Initialisation method.
XMMS_XFORM_BUILTIN(magic, "Magic file identifier", XMMS_VERSION, "Magic file identifier", xmms_magic_plugin_setup)
gint xmms_config_property_get_int(const xmms_config_property_t *prop)
Return the value of a config property as an int.
Definition: config.c:255
struct xmms_magic_ext_data_St xmms_magic_ext_data_t
#define xmms_log_error(fmt,...)
Definition: xmms_log.h:35
void xmms_xform_metadata_set_str(xmms_xform_t *xform, const char *key, const char *val)
Definition: xform.c:520
Methods provided by an xform plugin.
void xmms_xform_plugin_methods_set(xmms_xform_plugin_t *plugin, xmms_xform_methods_t *methods)
Should be called once from the plugin&#39;s setupfunc.
Definition: xform_plugin.c:53
gint64 xmms_xform_seek(xmms_xform_t *xform, gint64 offset, xmms_xform_seek_mode_t whence, xmms_error_t *err)
Change offset in stream.
Definition: xform.c:1120
enum xmms_magic_entry_operator_St xmms_magic_entry_operator_t
gboolean xmms_magic_extension_add(const gchar *mime, const gchar *ext)
Definition: magic.c:507
#define CMP(v1, entry, v2)
Definition: magic.c:42
xmms_config_property_t * xmms_xform_config_lookup(xmms_xform_t *xform, const gchar *path)
Definition: xform.c:1453
#define SWAP32(v, endian)
Definition: magic.c:35
const char * xmms_xform_indata_find_str(xmms_xform_t *xform, xmms_stream_type_key_t key)
Definition: xform.c:459
xmms_config_property_t * xmms_xform_plugin_config_property_register(xmms_xform_plugin_t *xform_plugin, const gchar *name, const gchar *default_value, xmms_object_handler_t cb, gpointer userdata)
Definition: xform_plugin.c:141
gint xmms_xform_read(xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err)
Read data from previous xform.
Definition: xform.c:1113
#define MIN(a, b)
Definition: xmmsc_util.h:36
void xmms_xform_outdata_type_add(xmms_xform_t *xform,...)
Definition: xform.c:436
gint(* read)(xmms_xform_t *, gpointer, gint, xmms_error_t *)
Read method.
#define XMMS_DBG(fmt,...)
Definition: xmms_log.h:32
struct xmms_magic_checker_St xmms_magic_checker_t
gint xmms_xform_peek(xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err)
Preview data from previous xform.
Definition: xform.c:1060
G_BEGIN_DECLS struct xmms_error_St xmms_error_t
struct xmms_config_property_St xmms_config_property_t
Definition: xmms_config.h:26
#define XMMS_XFORM_METHODS_INIT(m)
xmms_magic_entry_operator_St
Definition: magic.c:69
#define SWAP16(v, endian)
Definition: magic.c:28
gint64(* seek)(xmms_xform_t *, gint64, xmms_xform_seek_mode_t, xmms_error_t *)
Seek method.