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
26static 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
77typedef 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
93typedef 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
102typedef struct xmms_magic_ext_data_St {
103 gchar *type;
104 gchar *pattern;
106
107static void xmms_magic_tree_free (GNode *tree);
108
109static gchar *xmms_magic_match (xmms_magic_checker_t *c, const gchar *u);
110static guint xmms_magic_complexity (GNode *tree);
111
112static void
113xmms_magic_entry_free (xmms_magic_entry_t *e)
114{
115 g_free (e);
116}
117
119parse_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
154parse_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
182static gboolean
183parse_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
208static xmms_magic_entry_t *
209parse_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;
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
267static gboolean
268free_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
284static void
285xmms_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
291static GNode *
292xmms_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
346static gint
347read_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
361static gboolean
362node_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
411static gboolean
412tree_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
430static gchar *
431xmms_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
482static guint
483xmms_magic_complexity (GNode *tree)
484{
485 return g_node_n_nodes (tree, G_TRAVERSE_ALL);
486}
487
488static gint
489cb_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
506gboolean
507xmms_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
523gboolean
524xmms_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
582static gboolean
583xmms_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
614static gboolean
615xmms_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
638 "Magic file identifier",
639 XMMS_VERSION,
640 "Magic file identifier",
641 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_xform_plugin_St xmms_xform_plugin_t
Xform plugin.
struct xmms_xform_St xmms_xform_t
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
xmms_config_property_t * xmms_xform_config_lookup(xmms_xform_t *xform, const gchar *path)
Definition: xform.c:1453
void xmms_xform_plugin_indata_add(xmms_xform_plugin_t *plugin,...)
Add a valid input type to the plugin.
Definition: xform_plugin.c:79
void xmms_xform_outdata_type_add(xmms_xform_t *xform,...)
Definition: xform.c:436
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
gboolean xmms_magic_extension_add(const gchar *mime, const gchar *ext)
Definition: magic.c:507
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
void xmms_xform_plugin_methods_set(xmms_xform_plugin_t *plugin, xmms_xform_methods_t *methods)
Should be called once from the plugin's setupfunc.
Definition: xform_plugin.c:53
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
gboolean xmms_magic_add(const gchar *desc, const gchar *mime,...)
Definition: magic.c:524
#define XMMS_XFORM_METHODS_INIT(m)
struct xmms_magic_checker_St xmms_magic_checker_t
enum xmms_magic_entry_operator_St xmms_magic_entry_operator_t
#define SWAP16(v, endian)
Definition: magic.c:28
xmms_magic_entry_type_St
Definition: magic.c:60
@ XMMS_MAGIC_ENTRY_TYPE_UNKNOWN
Definition: magic.c:61
@ XMMS_MAGIC_ENTRY_TYPE_STRINGC
Definition: magic.c:66
@ XMMS_MAGIC_ENTRY_TYPE_INT32
Definition: magic.c:64
@ XMMS_MAGIC_ENTRY_TYPE_STRING
Definition: magic.c:65
@ XMMS_MAGIC_ENTRY_TYPE_BYTE
Definition: magic.c:62
@ XMMS_MAGIC_ENTRY_TYPE_INT16
Definition: magic.c:63
xmms_magic_entry_operator_St
Definition: magic.c:69
@ XMMS_MAGIC_ENTRY_OPERATOR_GREATER_THAN
Definition: magic.c:72
@ XMMS_MAGIC_ENTRY_OPERATOR_EQUAL
Definition: magic.c:70
@ XMMS_MAGIC_ENTRY_OPERATOR_NAND
Definition: magic.c:74
@ XMMS_MAGIC_ENTRY_OPERATOR_AND
Definition: magic.c:73
@ XMMS_MAGIC_ENTRY_OPERATOR_LESS_THAN
Definition: magic.c:71
#define SWAP32(v, endian)
Definition: magic.c:35
#define CMP(v1, entry, v2)
Definition: magic.c:42
struct xmms_magic_entry_St xmms_magic_entry_t
enum xmms_magic_entry_type_St xmms_magic_entry_type_t
struct xmms_magic_ext_data_St xmms_magic_ext_data_t
XMMS_XFORM_BUILTIN(magic, "Magic file identifier", XMMS_VERSION, "Magic file identifier", xmms_magic_plugin_setup)
Methods provided by an xform plugin.
gboolean(* init)(xmms_xform_t *)
Initialisation method.
gint64(* seek)(xmms_xform_t *, gint64, xmms_xform_seek_mode_t, xmms_error_t *)
Seek method.
gint(* read)(xmms_xform_t *, gpointer, gint, xmms_error_t *)
Read method.
void xmms_xform_metadata_set_str(xmms_xform_t *xform, const char *key, const char *val)
Definition: xform.c:520
const char * xmms_xform_indata_find_str(xmms_xform_t *xform, xmms_stream_type_key_t key)
Definition: xform.c:459
struct xmms_config_property_St xmms_config_property_t
Definition: xmms_config.h:26
#define xmms_log_error(fmt,...)
Definition: xmms_log.h:35
#define XMMS_DBG(fmt,...)
Definition: xmms_log.h:32
#define XMMS_MEDIALIB_ENTRY_PROPERTY_MIME
Definition: xmms_medialib.h:27
@ XMMS_STREAM_TYPE_MIMETYPE
@ XMMS_STREAM_TYPE_URL
@ XMMS_STREAM_TYPE_END
G_BEGIN_DECLS struct xmms_error_St xmms_error_t
#define MIN(a, b)
Definition: xmmsc_util.h:36