ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / libgnomevfs / gnome-vfs-mime-magic.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /*
4  * gnome-vfs-mime-magic.c
5  *
6  * Written by:
7  *    James Youngman (jay@gnu.org)
8  *
9  * Adatped to the GNOME needs by:
10  *    Elliot Lee (sopwith@cuc.edu)
11  * 
12  * Rewritten by:
13  *    Pavel Cisler <pavel@eazel.com>
14  */
15
16 #include <config.h>
17 #include "gnome-vfs-mime-magic.h"
18
19 #include <sys/types.h>
20
21 #include "gnome-vfs-mime-sniff-buffer-private.h"
22 #include "gnome-vfs-mime.h"
23 #include "gnome-vfs-private-utils.h"
24
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <stdlib.h>
32
33 #ifdef HAVE_WCTYPE_H
34 #include <wctype.h>
35 #include <wchar.h>
36 #endif
37
38 #include <glib/garray.h>
39 #include <glib/gmessages.h>
40 #include <glib/gstrfuncs.h>
41 #include <glib/gthread.h>
42 #include <glib/gutils.h>
43 #include <glib/gunicode.h>
44 static gboolean
45 is_octal_digit (char ch)
46 {
47         return ch >= '0' && ch <= '7';
48 }
49
50 static gboolean
51 is_hex_digit (char ch)
52 {
53         if (ch >= '0' && ch <= '9') {
54                 return TRUE;
55         }
56         if (ch >= 'a' && ch <= 'f') {
57                 return TRUE;
58         }
59
60         return (ch >= 'A' && ch <= 'F');
61 }
62
63 /* FIXME bugzilla.eazel.com 2760:
64  * should return error here
65  */
66 static guchar
67 read_octal_byte (const char **pos)
68 {
69         guchar retval = 0;
70         int count;
71
72         for (count = 0; count < 3; count++) {
73                 if (!is_octal_digit (**pos)) {
74                         g_error ("bad octal digit %c", **pos);
75                         return retval;
76                 }
77                 
78                 retval *= 8;
79                 retval += **pos - '0';
80                 (*pos)++;
81         }
82
83         return retval;
84 }
85
86 /* FIXME bugzilla.eazel.com 2760:
87  * should return error here
88  */
89 static guchar
90 read_hex_byte (const char **pos)
91 {
92         guchar retval = 0;
93         int count;
94
95         for (count = 0; ; count++) {
96                 if (!is_hex_digit (**pos)) {
97                         g_error ("bad hex digit %c", **pos);
98                         return retval;
99                 }
100                 if (**pos >= '0' && **pos <= '9') {
101                         retval += **pos - '0';
102                 } else {
103                         retval += g_ascii_tolower (**pos) - 'a' + 10;
104                 }
105
106                 (*pos)++;
107                 if (count >= 1) {
108                         break;
109                 }
110                 retval *= 16;
111         }
112
113         return retval;
114 }
115
116 /* FIXME bugzilla.eazel.com 2760:
117  * should return error here
118  */
119 static const char *
120 read_string_val (const char *scanner, char *intobuf, int max_len, guint16 *into_len)
121 {
122         char *intobufend;
123         char ch;
124
125         intobufend = intobuf + max_len - 1;
126         *into_len = 0;
127
128         while (*scanner && !g_ascii_isspace (*scanner) && *scanner != '#') {
129                 ch = *scanner++;
130
131                 switch (ch) {
132                 case '\\':
133                         switch (*scanner) {
134                         case 'x': 
135                                 /* read hex value */
136                                 scanner++;
137                                 ch = read_hex_byte (&scanner);
138                                 break;
139                         case '0': 
140                         case '1':
141                         case '2':
142                         case '3':
143                                 /* read octal value */
144                                 ch = read_octal_byte (&scanner);
145                                 break;
146                         case 'n': 
147                                 ch = '\n'; 
148                                 scanner++; 
149                                 break;
150                         default:
151                                 /* everything else is a literal */
152                                 ch = *scanner; 
153                                 scanner++; 
154                                 break;
155                         }
156                         break;
157                 default:
158                         break;
159                         /* already setup c/moved scanner */
160                 }
161                 if (intobuf < intobufend) {
162                         *intobuf++=ch;
163                         (*into_len)++;
164                 }
165         }
166
167         *intobuf = '\0';
168         return scanner;
169 }
170
171 static const char *
172 read_hex_pattern (const char *scanner, char *result, int length)
173 {
174         if (*scanner == '0') {
175                 scanner++;
176         }
177         if (*scanner++ != 'x') {
178                 return NULL;
179         }
180         for (;length > 0; length--) {
181                 if (!is_hex_digit (scanner[0]) || !is_hex_digit (scanner[1])) {
182                         return NULL;
183                 }
184                 *result++ = read_hex_byte (&scanner);
185         }
186
187         return scanner;
188 }
189
190 static gboolean
191 read_num_val(const char **offset, int bsize, int *result)
192 {
193         char fmttype, fmtstr[4];
194         const char *scanner = *offset;
195         
196         if (*scanner == '0') {
197                 if (g_ascii_tolower (scanner[1]) == 'x') {
198                         fmttype = 'x';
199                 } else {
200                         fmttype = 'o';
201                 }
202         } else {
203                 fmttype = 'u';
204         }
205
206         switch (bsize) {
207         case 1:
208                 fmtstr[0] = '%'; 
209                 fmtstr[1] = fmttype; 
210                 fmtstr[2] = '\0';
211                 if (sscanf (scanner, fmtstr, result) < 1) {
212                         return FALSE;
213                 }
214                 break;
215         case 2:
216                 fmtstr[0] = '%'; 
217                 fmtstr[1] = 'h'; 
218                 fmtstr[2] = fmttype; 
219                 fmtstr[3] = '\0';
220                 if (sscanf (scanner, fmtstr, result) < 1) {
221                         return FALSE;
222                 }
223                 break;
224         case 4:
225                 fmtstr[0] = '%'; 
226                 fmtstr[1] = fmttype; 
227                 fmtstr[2] = '\0';
228                 if (sscanf (scanner, fmtstr, result) < 1) {
229                         return FALSE;
230                 }
231                 break;
232         }
233
234         while (**offset && !g_ascii_isspace (**offset)) {
235                 (*offset)++;
236         }
237
238         return TRUE;
239 }
240
241 static const char *
242 eat_white_space (const char *scanner)
243 {
244         while (g_ascii_isspace (*scanner)) {
245                 scanner++;
246         }
247         return scanner;
248 }
249
250 static gboolean
251 match_pattern (const char *scanner, const char **resulting_scanner, const char *pattern)
252 {
253         if (strncmp(scanner, pattern, strlen (pattern)) == 0) {
254                 *resulting_scanner = scanner + strlen (pattern);
255                 return TRUE;
256         }
257         *resulting_scanner = scanner;
258         return FALSE;
259 }
260
261 GnomeMagicEntry *
262 _gnome_vfs_mime_magic_parse (const gchar *filename, gint *nents)
263 {
264         GArray *array;
265         GnomeMagicEntry newent, *retval;
266         FILE *infile;
267         const char *infile_name;
268         int bsize = 0;
269         char parsed_line [256];
270         const char *scanner;
271         int index;
272
273         infile_name = filename;
274
275         if (!infile_name) {
276                 return NULL;
277         }
278
279         infile = fopen (infile_name, "r");
280         if (!infile) {
281                 return NULL;
282         }
283
284         array = g_array_new (FALSE, FALSE, sizeof (GnomeMagicEntry));
285
286         while (fgets (parsed_line, sizeof (parsed_line), infile)) {
287                 scanner = parsed_line;
288
289                 /* eat the head */
290                 scanner = eat_white_space (scanner);
291
292                 if (!*scanner || *scanner == '#') {
293                         continue;
294                 }
295
296                 if (!g_ascii_isdigit (*scanner)) {
297                         continue;
298                 }
299
300                 if (sscanf (scanner, "%hu", &newent.range_start) < 1) {
301                         continue;
302                 }
303                 newent.range_end = newent.range_start;
304
305                 while (g_ascii_isdigit (*scanner)) {
306                         scanner++; /* eat the offset */
307                 }
308
309                 if (*scanner  == ':') {
310                         /* handle an offset range */
311                         scanner++; 
312                         if (sscanf (scanner, "%hu", &newent.range_end) < 1) {
313                                 continue;
314                         }
315                 }
316
317                 while (*scanner && !g_ascii_isspace (*scanner)) {
318                         scanner++; /* eat the offset */
319                 }
320
321                 scanner = eat_white_space (scanner);
322
323                 if (!*scanner || *scanner == '#') {
324                         continue;
325                 }
326
327                 if (match_pattern (scanner, &scanner, "byte")) {
328                         newent.type = T_BYTE;
329                 } else if (match_pattern (scanner, &scanner, "short")) {
330                         newent.type = T_SHORT;
331                 } else if (match_pattern (scanner, &scanner, "long")) {
332                         newent.type = T_LONG;
333                 } else if (match_pattern (scanner, &scanner, "string")) {
334                         newent.type = T_STR;
335                 } else if (match_pattern (scanner, &scanner, "date")) {
336                         newent.type = T_DATE;
337                 } else if (match_pattern (scanner, &scanner, "beshort")) {
338                         newent.type = T_BESHORT;
339                 } else if (match_pattern (scanner, &scanner, "belong")) {
340                         newent.type = T_BELONG;
341                 } else if (match_pattern (scanner, &scanner, "bedate")) {
342                         newent.type = T_BEDATE;
343                 } else if (match_pattern (scanner, &scanner, "leshort")) {
344                         newent.type = T_LESHORT;
345                 } else if (match_pattern (scanner, &scanner, "lelong")) {
346                         newent.type = T_LELONG;
347                 } else if (match_pattern (scanner, &scanner, "ledate")) {
348                         newent.type = T_LEDATE;
349                 } else
350                         continue; /* weird type */
351
352                 scanner = eat_white_space (scanner);
353                 if (!*scanner || *scanner == '#') {
354                         continue;
355                 }
356                 
357                 switch (newent.type) {
358                 case T_BYTE:
359                         bsize = 1;
360                         break;
361                         
362                 case T_SHORT:
363                 case T_BESHORT:
364                 case T_LESHORT:
365                         bsize = 2;
366                         break;
367                         
368                 case T_LONG:
369                 case T_BELONG:
370                 case T_LELONG:
371                         bsize = 4;
372                         break;
373                         
374                 case T_DATE:
375                 case T_BEDATE:
376                 case T_LEDATE:
377                         bsize = 4;
378                         break;
379                         
380                 default:
381                         /* do nothing */
382                         break;
383                 }
384
385                 if (newent.type == T_STR) {
386                         scanner = read_string_val (scanner, newent.pattern, 
387                                                    sizeof (newent.pattern), &newent.pattern_length);
388                 } else {
389                         newent.pattern_length = bsize;
390                         if (!read_num_val (&scanner, bsize, (int *)&newent.pattern)) {
391                                 continue;
392                         }
393                 }
394
395                 scanner = eat_white_space (scanner);
396                 if (!*scanner || *scanner == '#') {
397                         continue;
398                 }
399
400                 if (*scanner == '&') {
401                         scanner++;
402                         scanner = read_hex_pattern (scanner, &newent.mask [0], newent.pattern_length);
403                         if (!scanner) {
404                                 g_error ("bad mask");
405                                 continue;
406                         }
407                         newent.use_mask = TRUE;
408                         
409                         for (index = 0; index < newent.pattern_length; index++) {
410                                 /* Apply the mask to the pattern itself so we don't have to
411                                  * do it each time we compare it with the tested bytes.
412                                  */
413                                 newent.pattern[index] &= newent.mask[index];
414                         }
415                 } else {
416                         newent.use_mask = FALSE;
417                 }
418
419                 scanner = eat_white_space (scanner);
420                 if (!*scanner || *scanner == '#') {
421                         continue;
422                 }
423
424                 g_snprintf (newent.mimetype, sizeof (newent.mimetype), "%s", scanner);
425                 bsize = strlen (newent.mimetype) - 1;
426                 while (newent.mimetype [bsize] && g_ascii_isspace (newent.mimetype [bsize])) {
427                         newent.mimetype [bsize--] = '\0';
428                 }
429
430                 g_array_append_val (array, newent);
431         }
432         fclose(infile);
433
434         newent.type = T_END;
435         g_array_append_val (array, newent);
436
437         retval = (GnomeMagicEntry *)array->data;
438         if (nents) {
439                 *nents = array->len;
440         }
441
442         g_array_free (array, FALSE);
443
444         return retval;
445 }
446
447 static void 
448 endian_swap (guchar *result, const guchar *data, gsize length)
449 {
450         const guchar *source_ptr = data;
451         guchar *dest_ptr = result + length - 1;
452         while (dest_ptr >= result) {
453                 *dest_ptr-- = *source_ptr++;
454         }
455 }
456
457 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
458 #define FIRST_ENDIAN_DEPENDENT_TYPE T_BESHORT
459 #define LAST_ENDIAN_DEPENDENT_TYPE T_BEDATE
460 #else   
461 #define FIRST_ENDIAN_DEPENDENT_TYPE T_LESHORT
462 #define LAST_ENDIAN_DEPENDENT_TYPE T_LEDATE
463 #endif
464
465 static gboolean
466 try_one_pattern_on_buffer (const char *sniffed_stream, GnomeMagicEntry *magic_entry)
467 {
468         gboolean using_cloned_pattern;
469         char pattern_clone [48];
470         int index, count;
471         const char *pattern;
472
473         using_cloned_pattern = FALSE;
474         if (magic_entry->type >= FIRST_ENDIAN_DEPENDENT_TYPE && magic_entry->type <= LAST_ENDIAN_DEPENDENT_TYPE) { 
475                 /* Endian-convert the data we are trying to recognize to
476                  * our host endianness.
477                  */
478                 char swap_buffer [sizeof(magic_entry->pattern)];
479
480                 g_assert(magic_entry->pattern_length <= 4);
481
482                 memcpy (swap_buffer, sniffed_stream, magic_entry->pattern_length);
483
484                 endian_swap (pattern_clone, swap_buffer, magic_entry->pattern_length);
485                 sniffed_stream = &pattern_clone[0];
486                 using_cloned_pattern = TRUE;
487         }
488
489         if (magic_entry->use_mask) {
490                 /* Apply mask to the examined data. At this point the data in
491                  * sniffed_stream is in the same endianness as the mask.
492                  */ 
493
494                 if (!using_cloned_pattern) {
495                         memcpy (pattern_clone, sniffed_stream, magic_entry->pattern_length);
496                         using_cloned_pattern = TRUE;
497                         sniffed_stream = &pattern_clone[0];
498                 }
499
500                 for (index = 0; index < magic_entry->pattern_length; index++) {
501                         pattern_clone[index] &= magic_entry->mask[index];
502                 }
503         }
504
505         if (*magic_entry->pattern != *sniffed_stream) {
506                 return FALSE;
507         }
508         
509         for (count = magic_entry->pattern_length, pattern = magic_entry->pattern;
510              count > 0; count--) {
511                 if (*pattern++ != *sniffed_stream++) {
512                         return FALSE;
513                 }
514         }
515         return TRUE;
516 }
517
518 enum {
519         SNIFF_BUFFER_CHUNK = 32
520 };
521
522
523 static gboolean
524 gnome_vfs_mime_try_one_magic_pattern (GnomeVFSMimeSniffBuffer *sniff_buffer, 
525                                       GnomeMagicEntry *magic_entry)
526 {
527         int offset;
528
529         if (sniff_buffer->read_whole_file &&
530             sniff_buffer->buffer_length < magic_entry->range_end + magic_entry->pattern_length) {
531                 /* There's no place this pattern could actually match */
532                 return FALSE;
533         }
534
535         for (offset = magic_entry->range_start; offset <= magic_entry->range_end; offset++) {
536                 /* this check is done only as an optimization
537                  * _gnome_vfs_mime_sniff_buffer_get already implements the laziness.
538                  * This gets called a million times though and every bit performance
539                  * is valuable. This way we avoid making the call.
540                  */
541
542                 if (sniff_buffer->buffer_length < offset + magic_entry->pattern_length) {
543
544                         if (!sniff_buffer->read_whole_file) {
545                                 if (_gnome_vfs_mime_sniff_buffer_get (sniff_buffer, 
546                                                                      offset + magic_entry->pattern_length) != GNOME_VFS_OK) {
547                                         return FALSE;
548                                 }
549                         } else {
550                                 /* We have the entire file and the pattern won't fit. Return FALSE */
551                                 return FALSE;
552                         }
553                 }
554                 
555                 if (try_one_pattern_on_buffer (sniff_buffer->buffer + offset, magic_entry)) {
556                         return TRUE;
557                 }
558         }
559         return FALSE;
560 }
561
562 /* We lock this mutex whenever we modify global state in this module.  */
563 G_LOCK_DEFINE_STATIC (mime_magic_table_mutex);
564
565 static GnomeMagicEntry *mime_magic_table = NULL;
566
567 static GnomeMagicEntry *
568 gnome_vfs_mime_get_magic_table (void)
569 {
570         G_LOCK (mime_magic_table_mutex);
571
572         if (mime_magic_table == NULL) {
573                 mime_magic_table = _gnome_vfs_mime_magic_parse
574                         (SYSCONFDIR "/gnome-vfs-mime-magic" , NULL);
575         }
576
577         G_UNLOCK (mime_magic_table_mutex);
578
579         return mime_magic_table;
580 }
581
582 const char *
583 _gnome_vfs_mime_get_type_from_magic_table (GnomeVFSMimeSniffBuffer *buffer)
584 {
585         GnomeMagicEntry *magic_table;
586         
587         magic_table = gnome_vfs_mime_get_magic_table ();
588         if (magic_table == NULL) {
589                 return NULL;
590         }
591         
592         for (; magic_table->type != T_END; magic_table++) {
593                 if (gnome_vfs_mime_try_one_magic_pattern (buffer, magic_table)) {
594                         return magic_table->mimetype;
595                 }
596         }
597         return NULL;
598 }
599
600
601 GnomeMagicEntry *
602 gnome_vfs_mime_test_get_magic_table (const char *table_path)
603 {
604         G_LOCK (mime_magic_table_mutex);
605         if (mime_magic_table == NULL) {
606                 mime_magic_table = _gnome_vfs_mime_magic_parse (table_path, NULL);
607         }
608         G_UNLOCK (mime_magic_table_mutex);
609
610         return mime_magic_table;
611 }
612
613 #define HEX_DIGITS "0123456789abcdef"
614
615 static void
616 print_escaped_string (const guchar *string, int length)
617 {
618         for (; length > 0; length--, string++) {
619                 if (*string == '\\' || *string == '#') {
620                         /* escape \, #, etc. properly */
621                         printf ("\\%c", *string);
622                 } else if (g_ascii_isgraph (*string)) {
623                         /* everything printable except for white space can go directly */
624                         printf ("%c", *string);
625                 } else {
626                         /* everything else goes in hex */
627                         printf ("\\x%c%c", HEX_DIGITS[(*string) / 16], HEX_DIGITS[(*string) % 16]);
628                 }
629         }
630 }
631
632 static void
633 print_hex_pattern (const guchar *string, int length)
634 {
635         printf ("\\x");
636         for (; length > 0; length--, string++) {
637                 printf ("%c%c", HEX_DIGITS[(*string) / 16], HEX_DIGITS[(*string) % 16]);
638         }
639 }
640 void 
641 gnome_vfs_mime_dump_magic_table (void)
642 {
643         GnomeMagicEntry *magic_table;
644         
645         magic_table = gnome_vfs_mime_get_magic_table ();
646         if (magic_table == NULL) {
647                 return;
648         }
649         
650         for (; magic_table->type != T_END; magic_table++) {
651                 printf ("%d", magic_table->range_start);
652                 if (magic_table->range_start != magic_table->range_end) {
653                         printf (":%d", magic_table->range_end);
654                 }
655                 printf ("\t");
656                 switch (magic_table->type) {
657                 case T_BYTE:
658                         printf("byte");
659                         break;
660                 case T_SHORT:
661                         printf("short");
662                         break;
663                 case T_LONG:
664                         printf("long");
665                         break;
666                 case T_STR:
667                         printf("string");
668                         break;
669                 case T_DATE:
670                         printf("date");
671                         break;
672                 case T_BESHORT:
673                         printf("beshort");
674                         break;
675                 case T_BELONG:
676                         printf("belong");
677                         break;
678                 case T_BEDATE:
679                         printf("bedate");
680                         break;
681                 case T_LESHORT:
682                         printf("leshort");
683                         break;
684                 case T_LELONG:
685                         printf("lelong");
686                         break;
687                 case T_LEDATE:
688                         printf("ledate");
689                         break;
690                 default:
691                         break;
692                 }
693                 printf ("\t");
694                 print_escaped_string (magic_table->pattern, magic_table->pattern_length);
695                 if (magic_table->use_mask) {
696                         printf (" &");
697                         print_hex_pattern (magic_table->mask, magic_table->pattern_length);
698                 }
699                 printf ("\t%s\n", magic_table->mimetype);
700         }
701 }
702
703 void
704 _gnome_vfs_mime_clear_magic_table (void)
705 {
706         G_LOCK (mime_magic_table_mutex);
707         g_free (mime_magic_table);
708         mime_magic_table = NULL;
709         G_UNLOCK (mime_magic_table_mutex);
710 }
711
712 /**
713  * gnome_vfs_get_mime_type_for_buffer:
714  * @buffer: a sniff buffer referencing either a file or data in memory
715  *
716  * This routine uses a magic database to guess the mime type of the
717  * data represented by @buffer.
718  *
719  * Returns a pointer to an internal copy of the mime-type for @buffer.
720  */
721 const char *
722 gnome_vfs_get_mime_type_for_buffer (GnomeVFSMimeSniffBuffer *buffer)
723 {
724         return _gnome_vfs_get_mime_type_internal (buffer, NULL);
725 }
726
727 enum {
728         GNOME_VFS_TEXT_SNIFF_LENGTH = 256
729 };
730
731
732 /**
733  * _gnome_vfs_sniff_buffer_looks_like_text:
734  * @sniff_buffer: buffer to examine
735  *
736  * Return value: returns %TRUE if the contents of @sniff_buffer appear to
737  * be text.
738  **/
739 gboolean
740 _gnome_vfs_sniff_buffer_looks_like_text (GnomeVFSMimeSniffBuffer *sniff_buffer)
741 {
742         gchar *end;
743         
744         _gnome_vfs_mime_sniff_buffer_get (sniff_buffer, GNOME_VFS_TEXT_SNIFF_LENGTH);
745
746         if (sniff_buffer->buffer_length == 0) {
747                 return FALSE;
748         }
749         
750         if (g_utf8_validate (sniff_buffer->buffer, 
751                              sniff_buffer->buffer_length, (const gchar**)&end))
752         {
753                 return TRUE;
754         } else {
755                 /* Check whether the string was truncated in the middle of
756                  * a valid UTF8 char, or if we really have an invalid
757                  * UTF8 string
758                  */
759                 gint remaining_bytes = sniff_buffer->buffer_length;
760
761                 remaining_bytes -= (end-((gchar*)sniff_buffer->buffer));
762         
763                 if (g_utf8_get_char_validated(end, remaining_bytes) == -2)
764                         return TRUE;
765 #if defined(HAVE_WCTYPE_H) && defined (HAVE_MBRTOWC)
766                 else {
767                         size_t wlen;
768                         wchar_t wc;
769                         gchar *src, *end;
770                         mbstate_t state;
771
772                         src = sniff_buffer->buffer;
773                         end = sniff_buffer->buffer + sniff_buffer->buffer_length;
774                         
775                         memset (&state, 0, sizeof (state));
776                         while (src < end) {
777                                 /* Don't allow embedded zeros in textfiles */
778                                 if (*src == 0)
779                                         return FALSE;
780                                 
781                                 wlen = mbrtowc(&wc, src, end - src, &state);
782
783                                 if (wlen == (size_t)(-1)) {
784                                         /* Illegal mb sequence */
785                                         return FALSE;
786                                 }
787                                 
788                                 if (wlen == (size_t)(-2)) {
789                                         /* No complete mb char before end
790                                          * Probably a cut off char which is ok */
791                                         return TRUE;
792                                 }
793
794                                 if (wlen == 0) {
795                                         /* Don't allow embedded zeros in textfiles */
796                                         return FALSE;
797                                 }
798                                 
799                                 if (!iswspace (wc)  && !iswprint(wc)) {
800                                         /* Not a printable or whitspace
801                                          * Probably not a text file */
802                                         return FALSE;
803                                 }
804
805                                 src += wlen;
806                         }
807                         return TRUE;
808                 }
809 #endif /* defined(HAVE_WCTYPE_H) && defined (HAVE_MBRTOWC) */
810         }
811         return FALSE;
812 }
813
814 static int bitrates[2][15] = {
815         { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320},
816         { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }
817 };      
818
819 static int frequencies[2][3] = {
820         { 44100, 48000, 32000 },
821         { 22050, 24000, 16000 } 
822 };      
823
824 /*
825  * Return length of an MP3 frame using potential 32-bit header value.  See
826  * "http://www.dv.co.yu/mpgscript/mpeghdr.htm" for details on the header
827  * format.
828  *
829  * NOTE: As an optimization and because they are rare, this returns 0 for
830  * version 2.5 or free format MP3s.
831  */
832 static gsize
833 get_mp3_frame_length (unsigned long mp3_header)
834 {
835         int ver = 4 - ((mp3_header >> 19) & 3u);
836         int br = (mp3_header >> 12) & 0xfu;
837         int srf = (mp3_header >> 10) & 3u;
838
839         /* are frame sync and layer 3 bits set? */
840         if (((mp3_header & 0xffe20000ul) == 0xffe20000ul)
841                 /* good version? */
842                 && ((ver == 1) || (ver == 2))
843                 /* good bitrate index (not free or invalid)? */
844                 && (br > 0) && (br < 15)
845                 /* good sampling rate frequency index? */
846                 && (srf != 3)
847                 /* not using reserved emphasis value? */
848                 && ((mp3_header & 3u) != 2)) {
849                 /* then this is most likely the beginning of a valid frame */
850
851                 gsize length = (gsize) bitrates[ver - 1][br] * 144000;
852                 length /= frequencies[ver - 1][srf];
853                 return length += ((mp3_header >> 9) & 1u) - 4;
854         }
855         return 0;
856 }
857
858 static unsigned long
859 get_4_byte_value (const unsigned char *bytes)
860 {
861         unsigned long value = 0;
862         int count;
863
864         for (count = 0; count < 4; ++count) {
865                 value <<= 8;
866                 value |= *bytes++;
867         }
868         return value;
869 }
870
871 enum {
872         GNOME_VFS_MP3_SNIFF_LENGTH = 256
873 };
874
875 /**
876  * _gnome_vfs_sniff_buffer_looks_like_mp3:
877  * @sniff_buffer: buffer to examine
878  *
879  * Return value: returns %TRUE if the contents of @sniff_buffer appear to
880  * be an MP3.
881  **/
882 gboolean
883 _gnome_vfs_sniff_buffer_looks_like_mp3 (GnomeVFSMimeSniffBuffer *sniff_buffer)
884 {
885         unsigned long mp3_header;
886         int offset;
887         
888         if (_gnome_vfs_mime_sniff_buffer_get (sniff_buffer, GNOME_VFS_MP3_SNIFF_LENGTH) != GNOME_VFS_OK) {
889                 return FALSE;
890         }
891
892         /*
893          * Use algorithm described in "ID3 tag version 2.3.0 Informal Standard"
894          * at "http://www.id3.org/id3v2.3.0.html" to detect a valid header, "An
895          * ID3v2 tag can be detected with the following pattern:
896          *      $49 44 33 yy yy xx zz zz zz zz
897          * Where yy is less than $FF, xx is the 'flags' byte and zz is less than
898          * $80."
899          *
900          * The informal standard also says, "The ID3v2 tag size is encoded with
901          * four bytes where the most significant bit (bit 7) is set to zero in
902          * every byte, making a total of 28 bits.  The zeroed bits are ignored,
903          * so a 257 bytes long tag is represented as $00 00 02 01."
904          */
905         if (strncmp ((char *) sniff_buffer->buffer, "ID3", 3) == 0
906                 && (sniff_buffer->buffer[3] != 0xffu)
907                 && (sniff_buffer->buffer[4] != 0xffu)
908                 && (sniff_buffer->buffer[6] < 0x80u)
909                 && (sniff_buffer->buffer[7] < 0x80u)
910                 && (sniff_buffer->buffer[8] < 0x80u)
911                 && (sniff_buffer->buffer[9] < 0x80u)) {
912                 /* checks for existance of vorbis identification header */
913                 for (offset=10; offset < GNOME_VFS_MP3_SNIFF_LENGTH-7; offset++) {
914                         if (strncmp ((char *) &sniff_buffer->buffer[offset], 
915                                      "\x01vorbis", 7) == 0) {
916                                 return FALSE;
917                         }
918                 }
919                 return TRUE;
920         }
921
922         /*
923          * Scan through the first "GNOME_VFS_MP3_SNIFF_LENGTH" bytes of the
924          * buffer to find a potential 32-bit MP3 frame header.
925          */
926         mp3_header = 0;
927         for (offset = 0; offset < GNOME_VFS_MP3_SNIFF_LENGTH; offset++) {
928                 gsize length;
929
930                 mp3_header <<= 8;
931                 mp3_header |= sniff_buffer->buffer[offset];
932                 mp3_header &= 0xfffffffful;
933
934                 length = get_mp3_frame_length (mp3_header);
935
936                 if (length != 0) {
937                         /*
938                          * Since one frame is available, is there another frame
939                          * just to be sure this is more likely to be a real MP3
940                          * buffer?
941                          */
942                         offset += 1 + length;
943
944                         if (_gnome_vfs_mime_sniff_buffer_get (sniff_buffer, offset + 4) != GNOME_VFS_OK) {
945                                 return FALSE;
946                         }
947                         mp3_header = get_4_byte_value (&sniff_buffer->buffer[offset]);
948                         length = get_mp3_frame_length (mp3_header);
949
950                         if (length != 0) {
951                                 return TRUE;
952                         }
953                         break;
954                 }
955         }
956
957         return FALSE;
958 }