502e356207696be4009fc0db6581a95542349447
[framework/uifw/efreet.git] / src / lib / efreet_mime.c
1 /* vim: set sw=4 ts=4 sts=4 et: */
2
3 #ifdef HAVE_CONFIG_H
4 # include <config.h>
5 #endif
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <ctype.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13 #include <sys/mman.h>
14 #include <unistd.h>
15 #include <time.h>
16 #include <fcntl.h>
17 #include <fnmatch.h>
18
19 #ifdef _WIN32
20 # include <winsock2.h>
21 #endif
22
23 #ifdef HAVE_ARPA_INET
24 # include <arpa/inet.h>
25 #endif
26
27 #ifdef HAVE_ALLOCA_H
28 # include <alloca.h>
29 #elif defined __GNUC__
30 # define alloca __builtin_alloca
31 #elif defined _AIX
32 # define alloca __alloca
33 #elif defined _MSC_VER
34 # include <malloc.h>
35 # define alloca _alloca
36 #else
37 # include <stddef.h>
38 # ifdef  __cplusplus
39 extern "C"
40 # endif
41 void *alloca (size_t);
42 #endif
43
44 #include <Ecore.h>
45 #include <Ecore_File.h>
46
47 #include <Efreet.h>
48 #include <Efreet_Mime.h>
49 #include "efreet_private.h"
50
51 static Eina_List *globs = NULL;     /* contains Efreet_Mime_Glob structs */
52 static Eina_List *magics = NULL;    /* contains Efreet_Mime_Magic structs */
53 static Eina_Hash *wild = NULL;      /* contains *.ext and mime.types globs*/
54 static Eina_Hash *monitors = NULL;  /* contains file monitors */
55 static Eina_Hash *mime_icons = NULL; /* contains cache with mime->icons */
56 static Eina_Inlist *mime_icons_lru = NULL;
57 static unsigned int _init_count = 0;
58
59 /**
60  * @internal
61  * @brief Holds whether we are big/little endian
62  * @note This is set during efreet_mime_init based on
63  * a runtime check.
64  */
65 static enum
66 {
67     EFREET_ENDIAN_BIG = 0,
68     EFREET_ENDIAN_LITTLE = 1
69 } efreet_mime_endianess = EFREET_ENDIAN_BIG;
70
71 /*
72  * Buffer sized used for magic checks.  The default is good enough for the
73  * current set of magic rules.  This setting is only here for the future.
74  */
75 #define EFREET_MIME_MAGIC_BUFFER_SIZE 512
76
77 /*
78  * Minimum timeout in seconds between mime-icons cache flush.
79  */
80 #define EFREET_MIME_ICONS_FLUSH_TIMEOUT 60
81
82 /*
83  * Timeout in seconds, when older mime-icons items are expired.
84  */
85 #define EFREET_MIME_ICONS_EXPIRE_TIMEOUT 600
86
87 /*
88  * mime-icons maximum population.
89  */
90 #define EFREET_MIME_ICONS_MAX_POPULATION 512
91
92 /*
93  * If defined, dump mime-icons statistics after flush.
94  */
95 //#define EFREET_MIME_ICONS_DEBUG
96
97 /**
98  * Efreet_Mime_Glob
99  * @brief A parsed representation of a globs file
100  */
101 typedef struct Efreet_Mime_Glob Efreet_Mime_Glob;
102 struct Efreet_Mime_Glob
103 {
104     const char *glob;
105     const char *mime;
106 };
107
108 /**
109  * Efreet_Mime_Magic
110  * @brief A parsed representation of a magic file section
111  */
112 typedef struct Efreet_Mime_Magic Efreet_Mime_Magic;
113 struct Efreet_Mime_Magic
114 {
115     unsigned int priority;
116     const char *mime;
117     Eina_List *entries;
118 };
119
120 /**
121  * Efreet_Mime_Magic_Entry
122  * @brief A parsed representation of a magic file entry
123  */
124 typedef struct Efreet_Mime_Magic_Entry Efreet_Mime_Magic_Entry;
125 struct Efreet_Mime_Magic_Entry
126 {
127     unsigned int indent;
128     unsigned int offset;
129     unsigned int word_size;
130     unsigned int range_len;
131     unsigned short value_len;
132     char *mask;
133     char *value;
134 };
135
136 typedef struct Efreet_Mime_Icon_Entry_Head Efreet_Mime_Icon_Entry_Head;
137 struct Efreet_Mime_Icon_Entry_Head
138 {
139     EINA_INLIST; /* node of mime_icons_lru */
140     Eina_Inlist *list;
141     const char *mime;
142     time_t timestamp;
143 };
144
145 typedef struct Efreet_Mime_Icon_Entry Efreet_Mime_Icon_Entry;
146 struct Efreet_Mime_Icon_Entry
147 {
148     EINA_INLIST;
149     const char *icon;
150     const char *theme;
151     unsigned int size;
152 };
153
154
155 static int efreet_mime_glob_remove(const char *glob);
156 static void efreet_mime_mime_types_load(const char *file);
157 static void efreet_mime_shared_mimeinfo_globs_load(const char *file);
158 static void efreet_mime_shared_mimeinfo_magic_load(const char *file);
159 static void efreet_mime_shared_mimeinfo_magic_parse(char *data, int size);
160 static const char *efreet_mime_magic_check_priority(const char *file,
161                                                       unsigned int start,
162                                                       unsigned int end);
163 static int efreet_mime_init_files(void);
164 static const char *efreet_mime_special_check(const char *file);
165 static const char *efreet_mime_fallback_check(const char *file);
166 static void efreet_mime_glob_free(void *data);
167 static void efreet_mime_magic_free(void *data);
168 static void efreet_mime_magic_entry_free(void *data);
169 static int efreet_mime_glob_match(const char *str, const char *glob);
170 static int efreet_mime_glob_case_match(char *str, const char *glob);
171 static int efreet_mime_endian_check(void);
172
173 static void efreet_mime_monitor_add(const char *file);
174 static void efreet_mime_cb_update_file(void *data,
175                                         Ecore_File_Monitor *monitor,
176                                         Ecore_File_Event event,
177                                         const char *path);
178
179 static void efreet_mime_icons_flush(time_t now);
180 static void efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry);
181 static void efreet_mime_icon_entry_add(const char *mime,
182                                        const char *icon,
183                                        const char *theme,
184                                        unsigned int size);
185 static const char *efreet_mime_icon_entry_find(const char *mime,
186                                                const char *theme,
187                                                unsigned int size);
188 static void efreet_mime_icons_debug(void);
189
190 /**
191  * @return Returns 1 on success or 0 on failure
192  * @brief Initializes the efreet mime settings
193  */
194 EAPI int
195 efreet_mime_init(void)
196 {
197     _init_count++;
198     if (_init_count > 1)
199         return 1;
200
201     if (!ecore_init())
202         return 0;
203
204     if (!ecore_file_init())
205         return 0;
206
207     if (!efreet_init())
208         return 0;
209
210     efreet_mime_endianess = efreet_mime_endian_check();
211
212     monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del));
213
214     efreet_mime_type_cache_clear();
215
216     if (!efreet_mime_init_files())
217         return 0;
218
219     return 1;
220 }
221
222 /**
223  * @return Returns no value
224  * @brief Cleans up the efreet mime settings system
225  */
226 EAPI void
227 efreet_mime_shutdown(void)
228 {
229     void *d;
230
231     if (_init_count == 0)
232         return;
233     _init_count--;
234     if (_init_count > 0)
235         return;
236
237     efreet_mime_icons_debug();
238
239     IF_FREE_LIST(globs, efreet_mime_glob_free);
240     IF_FREE_LIST(magics, efreet_mime_magic_free);
241     IF_FREE_HASH(monitors);
242     IF_FREE_HASH(wild);
243     IF_FREE_HASH(mime_icons);
244
245     efreet_shutdown();
246     ecore_file_shutdown();
247     ecore_shutdown();
248 }
249
250 /**
251  * @param file: The file to find the mime type
252  * @return Returns mime type as a string
253  * @brief Retreive the mime type of a file
254  */
255 EAPI const char *
256 efreet_mime_type_get(const char *file)
257 {
258     const char *type = NULL;
259
260     if ((type = efreet_mime_special_check(file)))
261         return type;
262
263     /* Check magics with priority > 80 */
264     if ((type = efreet_mime_magic_check_priority(file, 0, 80)))
265         return type;
266
267     /* Check globs */
268     if ((type = efreet_mime_globs_type_get(file)))
269         return type;
270
271     /* Check rest of magics */
272     if ((type = efreet_mime_magic_check_priority(file, 80, 0)))
273         return type;
274
275     return efreet_mime_fallback_check(file);
276 }
277
278 /**
279  * @param mime: The name of the mime type
280  * @param theme: The name of the theme to search icons in
281  * @param size: The wanted size of the icon
282  * @return Returns mime type icon path as a string
283  * @brief Retreive the mime type icon for a file
284  */
285 EAPI char *
286 efreet_mime_type_icon_get(const char *mime, const char *theme, unsigned int size)
287 {
288     char *icon = NULL;
289     char *data;
290     Eina_List *icons  = NULL;
291     const char *env = NULL;
292     char *p = NULL, *pp = NULL, *ppp = NULL;
293     char buf[PATH_MAX];
294     const char *cache;
295
296     if (!mime || !theme || !size)
297         return NULL;
298
299     mime = eina_stringshare_add(mime);
300     theme = eina_stringshare_add(theme);
301     cache = efreet_mime_icon_entry_find(mime, theme, size);
302     if (cache)
303     {
304         eina_stringshare_del(mime);
305         eina_stringshare_del(theme);
306         return strdup(cache);
307     }
308
309     /* Standard icon name */
310     p = strdup(mime);
311     pp = p;
312     while (*pp)
313     {
314         if (*pp == '/') *pp = '-';
315         pp++;
316     }
317     icons = eina_list_append(icons, p);
318
319     /* Environment Based icon names */
320     if ((env = efreet_desktop_environment_get()))
321     {
322         snprintf(buf, sizeof(buf), "%s-mime-%s", env, p);
323         icons = eina_list_append(icons, strdup(buf));
324
325         snprintf(buf, sizeof(buf), "%s-%s", env, p);
326         icons = eina_list_append(icons, strdup(buf));
327     }
328
329     /* Mime prefixed icon names */
330     snprintf(buf, sizeof(buf), "mime-%s", p);
331     icons = eina_list_append(icons, strdup(buf));
332
333     /* Generic icons */
334     pp = strdup(p);
335     while ((ppp = strrchr(pp, '-')))
336     {
337         *ppp = '\0';
338
339         snprintf(buf, sizeof(buf), "%s-generic", pp);
340         icons = eina_list_append(icons, strdup(buf));
341
342         snprintf(buf, sizeof(buf), "%s", pp);
343         icons = eina_list_append(icons, strdup(buf));
344     }
345     FREE(pp);
346
347     /* Search for icons using list */
348     icon = efreet_icon_list_find(theme, icons, size);
349     while (icons)
350     {
351         data = eina_list_data_get(icons);
352         free(data);
353         icons = eina_list_remove_list(icons, icons);
354     }
355
356     efreet_mime_icon_entry_add(mime, eina_stringshare_add(icon), theme, size);
357
358     return icon;
359 }
360
361 EAPI void
362 efreet_mime_type_cache_clear(void)
363 {
364     if (mime_icons)
365     {
366         eina_hash_free(mime_icons);
367         mime_icons_lru = NULL;
368     }
369     mime_icons = eina_hash_pointer_new(EINA_FREE_CB(efreet_mime_icon_entry_head_free));
370 }
371
372 EAPI void
373 efreet_mime_type_cache_flush(void)
374 {
375     efreet_mime_icons_flush((time_t)ecore_loop_time_get());
376 }
377
378
379 /**
380  * @param file: The file to check the mime type
381  * @return Returns mime type as a string
382  * @brief Retreive the mime type of a file using magic
383  */
384 EAPI const char *
385 efreet_mime_magic_type_get(const char *file)
386 {
387     return efreet_mime_magic_check_priority(file, 0, 0);
388 }
389
390 /**
391  * @param file: The file to check the mime type
392  * @return Returns mime type as a string
393  * @brief Retreive the mime type of a file using globs
394  */
395 EAPI const char *
396 efreet_mime_globs_type_get(const char *file)
397 {
398     Eina_List *l;
399     Efreet_Mime_Glob *g;
400     char *sl, *p;
401     const char *s;
402     char *ext, *mime;
403
404     /* Check in the extension hash for the type */
405     ext = strchr(file, '.');
406     if (ext)
407     {
408         sl = alloca(strlen(ext) + 1);
409         for (s = ext, p = sl; *s; s++, p++) *p = tolower(*s);
410         *p = 0;
411         p = sl;
412         while (p)
413         {
414             p++;
415             if (p && (mime = eina_hash_find(wild, p))) return mime;
416             p = strchr(p, '.');
417         }
418     }
419
420     /* Fallback to the other globs if not found */
421     EINA_LIST_FOREACH(globs, l, g)
422     {
423         if (efreet_mime_glob_match(file, g->glob))
424             return g->mime;
425     }
426
427     ext = alloca(strlen(file) + 1);
428     for (s = file, p = ext; *s; s++, p++) *p = tolower(*s);
429     *p = 0;
430     EINA_LIST_FOREACH(globs, l, g)
431     {
432         if (efreet_mime_glob_case_match(ext, g->glob))
433             return g->mime;
434     }
435     return NULL;
436 }
437
438 /**
439  * @param file: The file to check the mime type
440  * @return Returns mime type as a string
441  * @brief Retreive the special mime type of a file
442  */
443 EAPI const char *
444 efreet_mime_special_type_get(const char *file)
445 {
446     return efreet_mime_special_check(file);
447 }
448
449 /**
450  * @param file: The file to check the mime type
451  * @return Returns mime type as a string
452  * @brief Retreive the fallback mime type of a file
453  */
454 EAPI const char *
455 efreet_mime_fallback_type_get(const char *file)
456 {
457     return efreet_mime_fallback_check(file);
458 }
459
460 /**
461  * @internal
462  * @return Returns the endianess
463  * @brief Retreive the endianess of the machine
464  */
465 static int
466 efreet_mime_endian_check(void)
467 {
468     int test = 1;
469     return (*((char*)(&test)));
470 }
471
472 /**
473  * @internal
474  * @param file: File to monitor
475  * @return Returns no value.
476  * @brief Creates a new file monitor if we aren't already monitoring the
477  * given file
478  */
479 static void
480 efreet_mime_monitor_add(const char *file)
481 {
482     Ecore_File_Monitor *fm = NULL;
483
484     /* if this is already in our hash then we're already monitoring so no
485      * reason to re-monitor */
486     if (eina_hash_find(monitors, file))
487         return;
488
489     if ((fm = ecore_file_monitor_add(file, efreet_mime_cb_update_file, NULL)))
490       {
491          eina_hash_del(monitors, file, NULL);
492          eina_hash_add(monitors, file, fm);
493       }
494 }
495
496 /**
497  * @internal
498  * @param datadirs: List of XDG data dirs
499  * @param datahome: Path to XDG data home directory
500  * @return Returns no value
501  * @brief Read all glob files in XDG data/home dirs.
502  * Also reads the /etc/mime.types file.
503  */
504 static void
505 efreet_mime_load_globs(Eina_List *datadirs, const char *datahome)
506 {
507     Eina_List *l;
508     char buf[4096];
509     const char *datadir = NULL;
510
511     IF_FREE_HASH(wild);
512     wild = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
513     while (globs)
514     {
515         efreet_mime_glob_free(eina_list_data_get(globs));
516         globs = eina_list_remove_list(globs, globs);
517     }
518
519     /*
520      * This is here for legacy reasons.  It is mentioned briefly
521      * in the spec and seems to still be quite valid.  It is
522      * loaded first so the globs files will override anything
523      * in here.
524     */
525     efreet_mime_mime_types_load("/etc/mime.types");
526
527     datadir = datahome;
528         snprintf(buf, sizeof(buf), "%s/mime/globs", datadir);
529         efreet_mime_shared_mimeinfo_globs_load(buf);
530
531     EINA_LIST_FOREACH(datadirs, l, datadir)
532     {
533         snprintf(buf, sizeof(buf), "%s/mime/globs", datadir);
534         efreet_mime_shared_mimeinfo_globs_load(buf);
535     }
536 }
537
538 /**
539  * @internal
540  * @param datadirs: List of XDG data dirs
541  * @param datahome: Path to XDG data home directory
542  * @return Returns no value
543  * @brief Read all magic files in XDG data/home dirs.
544  */
545 static void
546 efreet_mime_load_magics(Eina_List *datadirs, const char *datahome)
547 {
548     Eina_List *l;
549     char buf[4096];
550     const char *datadir = NULL;
551
552     while (magics)
553     {
554         efreet_mime_magic_free(eina_list_data_get(magics));
555         magics = eina_list_remove_list(magics, magics);
556     }
557
558     datadir = datahome;
559         snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
560         efreet_mime_shared_mimeinfo_magic_load(buf);
561
562     EINA_LIST_FOREACH(datadirs, l, datadir)
563     {
564         snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
565         efreet_mime_shared_mimeinfo_magic_load(buf);
566     }
567 }
568
569 /**
570  * @internal
571  * @param data: Data pointer passed to monitor_add
572  * @param monitor: Ecore_File_Monitor associated with this event
573  * @param event: The type of event
574  * @param path: Path to the file that was updated
575  * @return Returns no value
576  * @brief Callback for all file monitors.  Just reloads the appropriate
577  * list depending on which file changed.  If it was a magic file
578  * only the magic list is updated.  If it was a glob file or /etc/mime.types,
579  * the globs are updated.
580  */
581 static void
582 efreet_mime_cb_update_file(void *data __UNUSED__,
583                     Ecore_File_Monitor *monitor __UNUSED__,
584                     Ecore_File_Event event __UNUSED__,
585                     const char *path)
586 {
587     Eina_List *datadirs = NULL;
588     const char *datahome = NULL;
589
590     if (!(datahome = efreet_data_home_get()))
591         return;
592
593     if (!(datadirs = efreet_data_dirs_get()))
594         return;
595
596     if (strstr(path, "magic"))
597         efreet_mime_load_magics(datadirs, datahome);
598     else
599         efreet_mime_load_globs(datadirs, datahome);
600 }
601
602 /**
603  * @internal
604  * @param datadirs: List of XDG data dirs
605  * @param datahome: Path to XDG data home directory
606  * @return Returns 1 on success, 0 on failure
607  * @brief Initializes globs, magics, and monitors lists.
608  */
609 static int
610 efreet_mime_init_files(void)
611 {
612     Eina_List *l;
613     Eina_List *datadirs = NULL;
614     char buf[PATH_MAX];
615     const char *datahome, *datadir = NULL;
616
617     if (!(datahome = efreet_data_home_get()))
618         return 0;
619
620     if (!(datadirs = efreet_data_dirs_get()))
621         return 0;
622
623     /*
624      * Add our file monitors
625      * We watch the directories so we can watch for new files
626      */
627     datadir = datahome;
628         snprintf(buf, PATH_MAX, "%s/mime", datadir);
629         efreet_mime_monitor_add(buf);
630
631     EINA_LIST_FOREACH(datadirs, l, datadir)
632     {
633         snprintf(buf, PATH_MAX, "%s/mime", datadir);
634         efreet_mime_monitor_add(buf);
635     }
636     efreet_mime_monitor_add("/etc/mime.types");
637
638     /* Load our mime information */
639     efreet_mime_load_globs(datadirs, datahome);
640     efreet_mime_load_magics(datadirs, datahome);
641
642     return 1;
643 }
644
645 /**
646  * @internal
647  * @param file: File to examine
648  * @return Returns mime type if special file, else NULL
649  * @brief Returns a mime type based on the stat of a file.
650  * This is used first to catch directories and other special
651  * files.  A NULL return doesn't necessarily mean failure, but
652  * can also mean the file is regular.
653  * @note Mapping of file types to mime types:
654  * Stat Macro   File Type           Mime Type
655  * S_IFREG      regular             NULL
656  * S_IFIFO      named pipe (fifo)   inode/fifo
657  * S_IFCHR      character special   inode/chardevice
658  * S_IFDIR      directory           inode/directory
659  * S_IFBLK      block special       inode/blockdevice
660  * S_IFLNK      symbolic link       inode/symlink
661  * S_IFSOCK     socket              inode/socket
662  *
663  * This function can also return inode/mount-point.
664  * This is calculated by comparing the st_dev of the directory
665  * against that of it's parent directory.  If they differ it
666  * is considered a mount point.
667  */
668 static const char *
669 efreet_mime_special_check(const char *file)
670 {
671     struct stat s;
672     int path_len = 0;
673
674     if (!lstat(file, &s))
675     {
676         if (S_ISREG(s.st_mode))
677             return NULL;
678
679 #ifndef _WIN32
680         if (S_ISLNK(s.st_mode))
681             return "inode/symlink";
682 #endif
683
684         if (S_ISFIFO(s.st_mode))
685             return "inode/fifo";
686
687         if (S_ISCHR(s.st_mode))
688             return "inode/chardevice";
689
690         if (S_ISBLK(s.st_mode))
691             return "inode/blockdevice";
692
693 #ifndef _WIN32
694         if (S_ISSOCK(s.st_mode))
695             return "inode/socket";
696 #endif
697
698         if (S_ISDIR(s.st_mode))
699         {
700             struct stat s2;
701             char parent[PATH_MAX];
702             char path[PATH_MAX];
703
704             strncpy(path, file, PATH_MAX);
705
706             path_len = strlen(file);
707             strncpy(parent, path, PATH_MAX);
708
709             /* Kill any trailing slash */
710             parent[--path_len] = '\0';
711
712             /* Truncate to last slash */
713             while (parent[--path_len] != '/') parent[path_len] = '\0';
714
715             if (!lstat(parent, &s2))
716             {
717                 if (s.st_dev != s2.st_dev)
718                     return "inode/mount-point";
719             }
720
721             return "inode/directory";
722         }
723
724         return NULL;
725     }
726
727     return NULL;
728 }
729
730 /**
731  * @internal
732  * @param file: File to examine
733  * @return Returns mime type or NULL if the file doesn't exist
734  * @brief Returns text/plain if the file appears to contain text and
735  * returns application/octet-stream if it appears to be binary.
736  */
737 static const char *
738 efreet_mime_fallback_check(const char *file)
739 {
740     FILE *f = NULL;
741     char buf[32];
742     int i;
743
744     if (!(f = fopen(file, "r"))) return NULL;
745
746     i = fread(buf, 1, sizeof(buf), f);
747     fclose(f);
748
749     if (i == 0) return "application/octet-stream";
750
751     /*
752      * Check for ASCII control characters in the first 32 bytes.
753      * Line Feeds, carriage returns, and tabs are ignored as they are
754      * quite common in text files in the first 32 chars.
755      */
756     for (i -= 1; i >= 0; --i)
757     {
758         if ((buf[i] < 0x20) &&
759             (buf[i] != '\n') &&     /* Line Feed */
760             (buf[i] != '\r') &&     /* Carriage Return */
761             (buf[i] != '\t'))       /* Tab */
762             return "application/octet-stream";
763     }
764
765     return "text/plain";
766 }
767
768 /**
769  * @internal
770  * @param glob: Glob to search for
771  * @return Returns 1 on success, 0 on failure
772  * @brief Removes a glob from the list
773  */
774 static int
775 efreet_mime_glob_remove(const char *glob)
776 {
777     Efreet_Mime_Glob *mime = NULL;
778
779     if ((mime = eina_list_search_unsorted(globs, EINA_COMPARE_CB(strcmp), glob)))
780         {
781         globs = eina_list_remove(globs, mime);
782             IF_RELEASE(mime->glob);
783             IF_RELEASE(mime->mime);
784             FREE(mime);
785             return 1;
786         }
787
788     return 0;
789 }
790
791 /**
792  * @internal
793  * @param file: mime.types file to load
794  * @return Returns no value
795  * @brief Loads values from a mime.types style file
796  * into the globs list.
797  * @note Format:
798  * application/msaccess     mdb
799  * application/msword       doc dot
800  */
801 static void
802 efreet_mime_mime_types_load(const char *file)
803 {
804     FILE *f = NULL;
805     char buf[4096], mimetype[4096];
806     char ext[4096], *p = NULL, *pp = NULL;
807
808     f = fopen(file, "rb");
809     if (!f) return;
810     while (fgets(buf, sizeof(buf), f))
811     {
812         p = buf;
813         while (isspace(*p) && (*p != 0) && (*p != '\n')) p++;
814
815         if (*p == '#') continue;
816         if ((*p == '\n') || (*p == 0)) continue;
817
818         pp = p;
819         while (!isspace(*p) && (*p != 0) && (*p != '\n')) p++;
820
821         if ((*p == '\n') || (*p == 0)) continue;
822         strncpy(mimetype, pp, (p - pp));
823         mimetype[p - pp] = 0;
824
825         do
826         {
827             while (isspace(*p) && (*p != 0) && (*p != '\n')) p++;
828
829             if ((*p == '\n') || (*p == 0)) break;
830
831             pp = p;
832             while (!isspace(*p) && (*p != 0) && (*p != '\n')) p++;
833
834             strncpy(ext, pp, (p - pp));
835             ext[p - pp] = 0;
836
837             eina_hash_del(wild, ext, NULL);
838             eina_hash_add(wild, ext, (void*)eina_stringshare_add(mimetype));
839         }
840         while ((*p != '\n') && (*p != 0));
841     }
842     fclose(f);
843 }
844
845 /**
846  * @internal
847  * @param file: globs file to load
848  * @return Returns no value
849  * @brief Loads values from a mime.types style file
850  * into the globs list.
851  * @note Format:
852  * text/vnd.wap.wml:*.wml
853  * application/x-7z-compressed:*.7z
854  * application/vnd.corel-draw:*.cdr
855  * text/spreadsheet:*.sylk
856  */
857 static void
858 efreet_mime_shared_mimeinfo_globs_load(const char *file)
859 {
860     FILE *f = NULL;
861     char buf[4096], mimetype[4096], ext[4096], *p, *pp;
862     Efreet_Mime_Glob *mime = NULL;
863
864     f = fopen(file, "rb");
865     if (!f) return;
866
867     while (fgets(buf, sizeof(buf), f))
868     {
869         p = buf;
870         while (isspace(*p) && (*p != 0) && (*p != '\n')) p++;
871
872         if (*p == '#') continue;
873         if ((*p == '\n') || (*p == 0)) continue;
874
875         pp = p;
876         while ((*p != ':') && (*p != 0) && (*p != '\n')) p++;
877
878         if ((*p == '\n') || (*p == 0)) continue;
879         strncpy(mimetype, pp, (p - pp));
880         mimetype[p - pp] = 0;
881         p++;
882         pp = ext;
883
884         while ((*p != 0) && (*p != '\n'))
885         {
886             *pp = *p;
887             pp++;
888             p++;
889         }
890
891         *pp = 0;
892
893         if (ext[0] == '*' && ext[1] == '.')
894         {
895             eina_hash_del(wild, &(ext[2]), NULL);
896             eina_hash_add(wild, &(ext[2]),
897                           (void*)eina_stringshare_add(mimetype));
898         }
899         else
900         {
901             mime = NEW(Efreet_Mime_Glob, 1);
902             if (mime)
903             {
904                 mime->mime = eina_stringshare_add(mimetype);
905                 mime->glob = eina_stringshare_add(ext);
906                 if ((!mime->mime) || (!mime->glob))
907                 {
908                     IF_RELEASE(mime->mime);
909                     IF_RELEASE(mime->glob);
910                     FREE(mime);
911                 }
912                 else
913                 {
914                     efreet_mime_glob_remove(ext);
915                     globs = eina_list_append(globs, mime);
916                 }
917             }
918         }
919     }
920
921     fclose(f);
922 }
923
924 /**
925  * @internal
926  * @param in: Number to count the digits
927  * @return Returns number of digits
928  * @brief Calculates and returns the number of digits
929  * in a number.
930  */
931 static int
932 efreet_mime_count_digits(int in)
933 {
934     int i = 1, j = in;
935
936     if (j < 10) return 1;
937     while ((j /= 10) > 0) ++i;
938
939     return i;
940 }
941
942 /**
943  * @internal
944  * @param file: File to parse
945  * @return Returns no value
946  * @brief Loads a magic file and adds information to magics list
947  */
948 static void
949 efreet_mime_shared_mimeinfo_magic_load(const char *file)
950 {
951     int fd = -1, size;
952     char *data = (void *)-1;
953
954     if (!file) return;
955
956     size = ecore_file_size(file);
957     if (size <= 0) return;
958
959     fd = open(file, O_RDONLY);
960     if (fd == -1) return;
961
962     data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
963     if (data == MAP_FAILED)
964     {
965         close(fd);
966         return;
967     }
968
969     efreet_mime_shared_mimeinfo_magic_parse(data, size);
970
971     munmap(data, size);
972     close(fd);
973 }
974
975 /**
976  * @param data: The data from the file
977  * @return Returns no value
978  * @brief Parses a magic file
979  * @note Format:
980  *
981  * ----------------------------------------------------------------------
982  * |                     HEX                         |    ASCII         |
983  * ----------------------------------------------------------------------
984  * |4D 49 4D 45 2D 4D 61 67 69 63 00 0A 5B 39 30 3A  | MIME-Magic..[90: |
985  * |61 70 70 6C 69 63 61 74 69 6F 6E 2F 64 6F 63 62  | application/docb |
986  * |6F 6F 6B 2B 78 6D 6C 5D 0A 3E 30 3D 00 05 3C 3F  | ook+xml].>0=..<? |
987  * |78 6D 6C 0A 31 3E 30 3D 00 19 2D 2F 2F 4F 41 53  | xml.1>0=..-//OAS |
988  * |49 53 2F 2F 44 54 44 20 44 6F 63 42 6F 6F 6B 20  | IS//DTD DocBook  |
989  * |58 4D 4C 2B 31 30 31 0A 31 3E 30 3D 00 17 2D 2F  | XML+101.1>0=..-/ |
990  * ----------------------------------------------------------------------
991  *
992  * indent
993  *   The nesting depth of the rule, corresponding to the number of '>'
994  *   characters in the traditional file format.
995  * ">" start-offset
996  *     The offset into the file to look for a match.
997  * "=" value
998  *     Two bytes giving the (big-endian) length of the value, followed by the
999  *     value itself.
1000  * "&" mask
1001  *     The mask, which (if present) is exactly the same length as the value.
1002  * "~" word-size
1003  *     On little-endian machines, the size of each group to byte-swap.
1004  * "+" range-length
1005  *     The length of the region in the file to check.
1006  *
1007  * The indent, range-length, word-size and mask components are optional.
1008  * If missing, indent defaults to 0, range-length to 1, the word-size to 1,
1009  * and the mask to all 'one' bits.  In our case, mask is null as it is
1010  * quicker, uses less memory and will acheive the same exact effect.
1011  */
1012 static void
1013 efreet_mime_shared_mimeinfo_magic_parse(char *data, int size)
1014 {
1015     Efreet_Mime_Magic *mime = NULL;
1016     Efreet_Mime_Magic_Entry *entry = NULL;
1017     char *ptr;
1018
1019     ptr = data;
1020
1021     /* make sure we're a magic file */
1022     if (!ptr || (size < 12) || strncmp(ptr, "MIME-Magic\0\n", 12))
1023         return;
1024
1025     ptr += 12;
1026
1027     for (; (ptr - data) < size; )
1028     {
1029         if (*ptr == '[')
1030         {
1031             char *val, buf[512];
1032
1033             mime = NEW(Efreet_Mime_Magic, 1);
1034             magics = eina_list_append(magics, mime);
1035
1036             val = ++ptr;
1037             while ((*val != ':')) val++;
1038             memcpy(&buf, ptr, val - ptr);
1039             buf[val - ptr] = '\0';
1040
1041             mime->priority = atoi(buf);
1042             ptr = ++val;
1043
1044             while ((*val != ']')) val++;
1045             memcpy(&buf, ptr, val - ptr);
1046             buf[val - ptr] = '\0';
1047
1048             mime->mime = eina_stringshare_add(buf);
1049             ptr = ++val;
1050
1051             while (*ptr != '\n') ptr++;
1052         }
1053         else
1054         {
1055             short tshort;
1056
1057             if (!mime) continue;
1058             if (!entry)
1059             {
1060                 if (!(entry = NEW(Efreet_Mime_Magic_Entry, 1)))
1061                 {
1062                     IF_FREE_LIST(magics, efreet_mime_magic_free);
1063                     return;
1064                 }
1065
1066                 entry->indent = 0;
1067                 entry->offset = 0;
1068                 entry->value_len = 0;
1069                 entry->word_size = 1;
1070                 entry->range_len = 1;
1071                 entry->mask = NULL;
1072                 entry->value = NULL;
1073                 ptr++;
1074
1075                 mime->entries = eina_list_append(mime->entries, entry);
1076            }
1077
1078             switch(*ptr)
1079             {
1080                 case '>':
1081                     ptr ++;
1082                     entry->offset = atoi(ptr);
1083                     ptr += efreet_mime_count_digits(entry->offset);
1084                     break;
1085
1086                 case '=':
1087                     ptr++;
1088
1089                     memcpy(&tshort, ptr, sizeof(short));
1090                     entry->value_len = ntohs(tshort);
1091                     ptr += 2;
1092
1093                     entry->value = NEW(1, entry->value_len);
1094                     memcpy(entry->value, ptr, entry->value_len);
1095                     ptr += entry->value_len;
1096                     break;
1097
1098                 case '&':
1099                     ptr++;
1100                     entry->mask = NEW(1, entry->value_len);
1101                     memcpy(entry->mask, ptr, entry->value_len);
1102                     ptr += entry->value_len;
1103                     break;
1104
1105                 case '~':
1106                     ptr++;
1107                     entry->word_size = atoi(ptr);
1108                     if (((entry->word_size != 0)
1109                             && (entry->word_size != 1)
1110                             && (entry->word_size != 2)
1111                             && (entry->word_size != 4))
1112                             || (entry->value_len % entry->word_size))
1113                     {
1114                         /* Invalid, Destroy */
1115                         FREE(entry->value);
1116                         FREE(entry->mask);
1117                         FREE(entry);
1118
1119                         while (*ptr != '\n') ptr++;
1120                         break;
1121                     }
1122
1123                     if (efreet_mime_endianess == EFREET_ENDIAN_LITTLE)
1124                     {
1125                         int j;
1126
1127                         for (j = 0; j < entry->value_len; j += entry->word_size)
1128                         {
1129                             if (entry->word_size == 2)
1130                             {
1131                                 ((short*)entry->value)[j] =
1132                                               ntohs(((short*)entry->value)[j]);
1133
1134                                 if (entry->mask)
1135                                     ((short*)entry->mask)[j] =
1136                                               ntohs(((short*)entry->mask)[j]);
1137                             }
1138                             else if (entry->word_size == 4)
1139                             {
1140                                 ((int*)entry->value)[j] =
1141                                               ntohl(((int*)entry->value)[j]);
1142
1143                                 if (entry->mask)
1144                                     ((int*)entry->mask)[j] =
1145                                               ntohl(((int*)entry->mask)[j]);
1146                             }
1147                         }
1148                     }
1149
1150                     ptr += efreet_mime_count_digits(entry->word_size);
1151                     break;
1152
1153                 case '+':
1154                     ptr++;
1155                     entry->range_len = atoi(ptr);
1156                     ptr += efreet_mime_count_digits(entry->range_len);
1157                     break;
1158
1159                 case '\n':
1160                     ptr++;
1161                     entry = NULL;
1162                     break;
1163
1164                 default:
1165                     if (isdigit(*ptr))
1166                     {
1167                         entry->indent = atoi(ptr);
1168                         ptr += efreet_mime_count_digits(entry->indent);
1169                     }
1170                     break;
1171             }
1172         }
1173     }
1174 /*
1175     if (entry)
1176     {
1177         IF_FREE(entry->value);
1178         IF_FREE(entry->mask);
1179         FREE(entry);
1180     }
1181  */
1182 }
1183
1184 /**
1185  * @internal
1186  * @param file: File to check
1187  * @param start: Start priority, if 0 start at beginning
1188  * @param end: End priority, should be less then start
1189  * unless start
1190  * @return Returns mime type for file if found, NULL if not
1191  * @brief Applies magic rules to a file given a start and end priority
1192  */
1193 static const char *
1194 efreet_mime_magic_check_priority(const char *file,
1195                                   unsigned int start,
1196                                   unsigned int end)
1197 {
1198     Efreet_Mime_Magic *m = NULL;
1199     Efreet_Mime_Magic_Entry *e = NULL;
1200     Eina_List *l, *ll;
1201     FILE *f = NULL;
1202     unsigned int i = 0, offset = 0,level = 0, match = 0, bytes_read = 0;
1203     const char *last_mime = NULL;
1204     char c, v, buf[EFREET_MIME_MAGIC_BUFFER_SIZE];
1205
1206     f = fopen(file, "rb");
1207     if (!f) return NULL;
1208
1209     if (!magics)
1210     {
1211         fclose(f);
1212         return NULL;
1213     }
1214
1215     if ((bytes_read = fread(buf, 1, sizeof(buf), f)) == 0)
1216     {
1217         fclose(f);
1218         return NULL;
1219     }
1220
1221     EINA_LIST_FOREACH(magics, l, m)
1222     {
1223         if ((start != 0) && (m->priority > start))
1224             continue;
1225
1226         if (m->priority < end)
1227             break;
1228
1229         EINA_LIST_FOREACH(m->entries, ll, e)
1230         {
1231             if ((level < e->indent) && !match)
1232                 continue;
1233
1234             if ((level >= e->indent) && !match)
1235                 level = e->indent;
1236
1237             else if ((level > e->indent) && match)
1238             {
1239                 fclose(f);
1240                 if (last_mime) return last_mime;
1241             }
1242
1243             for (offset = e->offset; offset < e->offset + e->range_len; offset++)
1244             {
1245                 if (((offset + e->value_len) > bytes_read) &&
1246                         (fseek(f, offset, SEEK_SET) == -1))
1247                     break;
1248
1249                 match = 1;
1250                 for (i = 0; i < e->value_len; ++i)
1251                 {
1252                     if (offset + e->value_len > bytes_read)
1253                         c = fgetc(f);
1254                     else
1255                         c = buf[offset + i];
1256
1257                     v = e->value[i];
1258                     if (e->mask) v &= e->mask[i];
1259
1260                     if (!(c == v))
1261                     {
1262                         match = 0;
1263                         break;
1264                     }
1265                 }
1266
1267                 if (match)
1268                 {
1269                     level += 1;
1270                     last_mime = m->mime;
1271                     break;
1272                 }
1273             }
1274         }
1275     }
1276     fclose(f);
1277
1278     return NULL;
1279 }
1280
1281 /**
1282  * @internal
1283  * @param data: Data pointer that is being destroyed
1284  * @return Returns no value
1285  * @brief Callback for globs destroy
1286  */
1287 static void
1288 efreet_mime_glob_free(void *data)
1289 {
1290     Efreet_Mime_Glob *m = data;
1291
1292     IF_RELEASE(m->mime);
1293     IF_RELEASE(m->glob);
1294     IF_FREE(m);
1295 }
1296
1297 /**
1298  * @internal
1299  * @param data: Data pointer that is being destroyed
1300  * @return Returns no value
1301  * @brief Callback for magics destroy
1302  */
1303 static void
1304 efreet_mime_magic_free(void *data)
1305 {
1306     Efreet_Mime_Magic *m = data;
1307     Efreet_Mime_Magic_Entry *entry = NULL;
1308
1309     IF_RELEASE(m->mime);
1310     while (m->entries)
1311     {
1312         entry = eina_list_data_get(m->entries);
1313         efreet_mime_magic_entry_free(entry);
1314         m->entries = eina_list_remove_list(m->entries, m->entries);
1315     }
1316     IF_FREE(m);
1317 }
1318
1319 /**
1320  * @internal
1321  * @param data: Data pointer that is being destroyed
1322  * @return Returns no value
1323  * @brief Callback for magic entry destroy
1324  */
1325 static void
1326 efreet_mime_magic_entry_free(void *data)
1327 {
1328     Efreet_Mime_Magic_Entry *e = data;
1329
1330     IF_FREE(e->mask);
1331     IF_FREE(e->value);
1332     IF_FREE(e);
1333 }
1334
1335
1336 /**
1337  * @internal
1338  * @param str: String (filename) to match
1339  * @param glob: Glob to match str to
1340  * @return Returns 1 on success, 0 on failure
1341  * @brief Compares str to glob, case sensitive
1342  */
1343 static int
1344 efreet_mime_glob_match(const char *str, const char *glob)
1345 {
1346     if (!str || !glob) return 0;
1347     if (glob[0] == 0)
1348     {
1349         if (str[0] == 0) return 1;
1350         return 0;
1351     }
1352     if (!fnmatch(glob, str, 0)) return 1;
1353     return 0;
1354 }
1355
1356 /**
1357  * @internal
1358  * @param str: String (filename) to match
1359  * @param glob: Glob to match str to
1360  * @return Returns 1 on success, 0 on failure
1361  * @brief Compares str to glob, case insensitive (expects str already in lower case)
1362  */
1363 static int
1364 efreet_mime_glob_case_match(char *str, const char *glob)
1365 {
1366     const char *p;
1367     char *tglob, *tp;
1368
1369     if (!str || !glob) return 0;
1370     if (glob[0] == 0)
1371     {
1372         if (str[0] == 0) return 1;
1373         return 0;
1374     }
1375     tglob = alloca(strlen(glob) + 1);
1376     for (tp = tglob, p = glob; *p; p++, tp++) *tp = tolower(*p);
1377     *tp = 0;
1378     if (!fnmatch(str, tglob, 0)) return 1;
1379     return 0;
1380 }
1381
1382 static void
1383 efreet_mime_icons_flush(time_t now)
1384 {
1385     Eina_Inlist *l;
1386     static time_t old = 0;
1387     int todo;
1388
1389     if (now - old < EFREET_MIME_ICONS_FLUSH_TIMEOUT)
1390         return;
1391     old = now;
1392
1393     todo = eina_hash_population(mime_icons) - EFREET_MIME_ICONS_MAX_POPULATION;
1394     if (todo <= 0)
1395         return;
1396
1397     l = mime_icons_lru->last; /* mime_icons_lru is not NULL, since todo > 0 */
1398     for (; todo > 0; todo--)
1399     {
1400         Efreet_Mime_Icon_Entry_Head *entry = (Efreet_Mime_Icon_Entry_Head *)l;
1401         Eina_Inlist *prev = l->prev;
1402
1403         mime_icons_lru = eina_inlist_remove(mime_icons_lru, l);
1404         eina_hash_del_by_key(mime_icons, entry->mime);
1405         l = prev;
1406     }
1407
1408     efreet_mime_icons_debug();
1409 }
1410
1411 static void
1412 efreet_mime_icon_entry_free(Efreet_Mime_Icon_Entry *node)
1413 {
1414     eina_stringshare_del(node->icon);
1415     eina_stringshare_del(node->theme);
1416     free(node);
1417 }
1418
1419 static void
1420 efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry)
1421 {
1422     while (entry->list)
1423     {
1424         Efreet_Mime_Icon_Entry *n = (Efreet_Mime_Icon_Entry *)entry->list;
1425         entry->list = eina_inlist_remove(entry->list, entry->list);
1426         efreet_mime_icon_entry_free(n);
1427     }
1428
1429     eina_stringshare_del(entry->mime);
1430     free(entry);
1431 }
1432
1433 static Efreet_Mime_Icon_Entry *
1434 efreet_mime_icon_entry_new(const char *icon,
1435                            const char *theme,
1436                            unsigned int size)
1437 {
1438     Efreet_Mime_Icon_Entry *entry;
1439
1440     entry = malloc(sizeof(*entry));
1441     if (!entry)
1442         return NULL;
1443
1444     entry->icon = icon;
1445     entry->theme = theme;
1446     entry->size = size;
1447
1448     return entry;
1449 }
1450
1451 static void
1452 efreet_mime_icon_entry_add(const char *mime,
1453                            const char *icon,
1454                            const char *theme,
1455                            unsigned int size)
1456 {
1457     Efreet_Mime_Icon_Entry_Head *entry;
1458     Efreet_Mime_Icon_Entry *n;
1459
1460     n = efreet_mime_icon_entry_new(icon, theme, size);
1461     if (!n)
1462         return;
1463     entry = eina_hash_find(mime_icons, mime);
1464
1465     if (entry)
1466     {
1467         Eina_Inlist *l;
1468
1469         l = EINA_INLIST_GET(n);
1470         entry->list = eina_inlist_prepend(entry->list, l);
1471
1472         l = EINA_INLIST_GET(entry);
1473         mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1474     }
1475     else
1476     {
1477         Eina_Inlist *l;
1478
1479         entry = malloc(sizeof(*entry));
1480         if (!entry)
1481         {
1482             efreet_mime_icon_entry_free(n);
1483             return;
1484         }
1485
1486         l = EINA_INLIST_GET(n);
1487         entry->list = eina_inlist_prepend(NULL, l);
1488         entry->mime = mime;
1489         eina_hash_direct_add(mime_icons, mime, entry);
1490
1491         l = EINA_INLIST_GET(entry);
1492         mime_icons_lru = eina_inlist_prepend(mime_icons_lru, l);
1493     }
1494
1495     entry->timestamp = (time_t)ecore_loop_time_get();
1496     efreet_mime_icons_flush(entry->timestamp);
1497 }
1498
1499 static const char *
1500 efreet_mime_icon_entry_find(const char *mime,
1501                             const char *theme,
1502                             unsigned int size)
1503 {
1504     Efreet_Mime_Icon_Entry_Head *entry;
1505     Efreet_Mime_Icon_Entry *n;
1506
1507     entry = eina_hash_find(mime_icons, mime);
1508     if (!entry)
1509         return NULL;
1510
1511     EINA_INLIST_FOREACH(entry->list, n)
1512     {
1513         if ((n->theme == theme) && (n->size == size))
1514         {
1515             Eina_Inlist *l;
1516
1517             l = EINA_INLIST_GET(n);
1518             if (entry->list != l)
1519                 entry->list = eina_inlist_promote(entry->list, l);
1520
1521             l = EINA_INLIST_GET(entry);
1522             if (mime_icons_lru != l)
1523                 mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1524
1525             entry->timestamp = (time_t)ecore_loop_time_get();
1526             return n->icon;
1527         }
1528     }
1529
1530     return NULL;
1531 }
1532
1533 #ifdef EFREET_MIME_ICONS_DEBUG
1534 static void
1535 efreet_mime_icons_debug(void)
1536 {
1537     time_t now = (time_t)ecore_loop_time_get();
1538     Efreet_Mime_Icon_Entry_Head *entry;
1539     EINA_INLIST_FOREACH(mime_icons_lru, entry)
1540     {
1541         Efreet_Mime_Icon_Entry *n;
1542
1543         if ((now > 0) &&
1544             (now - entry->timestamp >= EFREET_MIME_ICONS_EXPIRE_TIMEOUT))
1545         {
1546             puts("*** FOLLOWING ENTRIES ARE AGED AND CAN BE EXPIRED ***");
1547             now = 0;
1548         }
1549
1550         printf("mime-icon entry: '%s' last used: %s",
1551                entry->mime, ctime(&entry->timestamp));
1552
1553         EINA_INLIST_FOREACH(entry->list, n)
1554             printf("\tsize: %3u theme: '%s' icon: '%s'\n",
1555                    n->theme, n->size, n->icon);
1556     }
1557 }
1558 #else
1559 static void
1560 efreet_mime_icons_debug(void)
1561 {
1562 }
1563 #endif